diff --git a/.gitignore b/.gitignore index b1b97f583f7..cb0de77c31d 100644 --- a/.gitignore +++ b/.gitignore @@ -166,6 +166,7 @@ resources/environment.sh /core/VERSION.md **/node_modules/ +tsconfig.tsbuildinfo # Windows system files Thumbs.db @@ -174,6 +175,13 @@ Thumbs.db # details /xcodebuild-scripts.log +# +# Code coverage tools for JS/TS - nyc and c8 +# +.nyc_output/ +lcov-report/ +lcov.info + # Linux packaging related /debian/ /results/ diff --git a/.nycrc.json b/.nycrc.json new file mode 100644 index 00000000000..ee788b80463 --- /dev/null +++ b/.nycrc.json @@ -0,0 +1,6 @@ +{ + "check-coverage": true, + "temp-directory": ".nyc_output", + "lines": 90, + "reporter": "text" +} diff --git a/common/include/km_types.h b/common/include/km_types.h index 113647d3a0a..bf25b354b51 100644 --- a/common/include/km_types.h +++ b/common/include/km_types.h @@ -1,5 +1,7 @@ #pragma once +#include + /* #if defined(_WIN32) || defined(_WIN64) #define snprintf _snprintf diff --git a/common/include/kmx_file.h b/common/include/kmx_file.h index 91df31c3b22..4519e0ccef3 100644 --- a/common/include/kmx_file.h +++ b/common/include/kmx_file.h @@ -14,6 +14,7 @@ namespace kbp { namespace kmx { #endif +#define KMX_MAX_ALLOWED_FILE_SIZE (128 * 1024 * 1024) /* 128MB */ /* */ #define KEYMAN_LAYOUT_DEFAULT 0x000005FE @@ -48,8 +49,10 @@ namespace kmx { #define VERSION_140 0x00000E00 #define VERSION_150 0x00000F00 +#define VERSION_160 0x00001000 + #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_150 +#define VERSION_MAX VERSION_160 // // Backspace types @@ -261,6 +264,9 @@ namespace kmx { #define KF_LOGICALLAYOUT 0x0008 #define KF_AUTOMATICVERSION 0x0010 +// 16.0: Support for LDML Keyboards in KMXPlus file format +#define KF_KMXPLUS 0x0020 + #define HK_ALT 0x00010000 #define HK_CTRL 0x00020000 #define HK_SHIFT 0x00040000 @@ -316,7 +322,7 @@ struct COMP_KEYBOARD { KMX_DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400 - KMX_DWORD dwCheckSum; // 0008 As stored in keyboard + KMX_DWORD dwCheckSum; // 0008 As stored in keyboard. DEPRECATED as of 16.0 KMX_DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts KMX_DWORD IsRegistered; // 0010 KMX_DWORD version; // 0014 keyboard version @@ -335,7 +341,20 @@ struct COMP_KEYBOARD { KMX_DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file KMX_DWORD dwBitmapSize; // 003C size in bytes of the bitmaps - }; +}; + +struct COMP_KEYBOARD_KMXPLUSINFO { + KMX_DWORD dpKMXPlus; // 0040 offset of KMXPlus data, header is first + KMX_DWORD dwKMXPlusSize; // 0044 size in bytes of entire KMXPlus data +}; + +/** + * Only valid if comp_keyboard.dwFlags&KF_KMXPLUS + */ +struct COMP_KEYBOARD_EX { + COMP_KEYBOARD header; // 0000 see COMP_KEYBOARD + COMP_KEYBOARD_KMXPLUSINFO kmxplus; // 0040 see COMP_KEYBOARD_EXTRA +}; typedef COMP_KEYBOARD *PCOMP_KEYBOARD; typedef COMP_STORE *PCOMP_STORE; @@ -359,4 +378,4 @@ static_assert(sizeof(COMP_KEYBOARD) == KEYBOARDFILEHEADER_SIZE, "COMP_KEYBOARD m } // namespace kmx } // namespace kbp } // namespace km -#endif \ No newline at end of file +#endif diff --git a/common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json b/common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json index 0aec7c9d1c3..8bdbf7f66ad 100644 --- a/common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json +++ b/common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json @@ -86,7 +86,7 @@ "layer": { "$ref": "#/definitions/layer-id" }, "nextlayer": { "$ref": "#/definitions/layer-id" }, "font": { "$ref": "#/definitions/font-spec" }, - "fontsize": { "type": "string" }, + "fontsize": { "$ref": "#/definitions/fontsize-spec" }, "sp": { "$ref": "#/definitions/key-sp" }, "pad": { "$ref" : "#/definitions/key-pad" }, "width": { "$ref" : "#/definitions/key-width" }, @@ -141,7 +141,7 @@ "layer": { "$ref": "#/definitions/layer-id" }, "nextlayer": { "$ref": "#/definitions/layer-id" }, "font": { "$ref": "#/definitions/font-spec" }, - "fontsize": { "type": "string" }, + "fontsize": { "$ref": "#/definitions/fontsize-spec" }, "sp": { "$ref": "#/definitions/key-sp" }, "pad": { "$ref" : "#/definitions/key-pad" }, "width": { "$ref" : "#/definitions/key-width" } diff --git a/common/schemas/keyman-touch-layout/keyman-touch-layout.spec.json b/common/schemas/keyman-touch-layout/keyman-touch-layout.spec.json index a7fc1772dce..18dc8ef549d 100644 --- a/common/schemas/keyman-touch-layout/keyman-touch-layout.spec.json +++ b/common/schemas/keyman-touch-layout/keyman-touch-layout.spec.json @@ -88,7 +88,7 @@ "text": { "type": "string" }, "layer": { "$ref": "#/definitions/layer-id" }, "nextlayer": { "$ref": "#/definitions/layer-id" }, - "fontsize": { "type": "string" }, + "fontsize": { "$ref": "#/definitions/fontsize-spec" }, "font": { "$ref": "#/definitions/font-spec" }, "dk": { "type": "string" }, "sp": { "$ref" : "#/definitions/numeric" }, @@ -129,7 +129,7 @@ "sp": { "$ref" : "#/definitions/numeric" }, "pad": { "$ref" : "#/definitions/numeric" }, "width": { "$ref" : "#/definitions/numeric" }, - "fontsize": { "type": "string" }, + "fontsize": { "$ref": "#/definitions/fontsize-spec" }, "font": { "$ref": "#/definitions/font-spec" }, "dk": { "type": "string" } }, diff --git a/common/schemas/kpj/README.md b/common/schemas/kpj/README.md new file mode 100644 index 00000000000..533b672b659 --- /dev/null +++ b/common/schemas/kpj/README.md @@ -0,0 +1,13 @@ +# kpj.xsd + +**Note:** `KeymanDeveloperProject.Options.Version` is currently implicitly +always '1.0'. It will be required for version 2.0 and later of the format. + +## 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, + which defaults to '$PROJECTPATH/source', and Options/BuildPath now defaults to + '$PROJECTPATH/build'. + +## 2023-02-25 1.0 +* Initial version 1.0, based on Delphi classes Keyman.Developer.System.Project.* diff --git a/common/schemas/kpj/kpj.schema.json b/common/schemas/kpj/kpj.schema.json new file mode 100644 index 00000000000..77721345f28 --- /dev/null +++ b/common/schemas/kpj/kpj.schema.json @@ -0,0 +1,127 @@ +{ + "title": "kpj.xsd", + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "KeymanDeveloperProject": { + "properties": { + "Options": { + "$ref": "#/definitions/Options" + }, + "Files": { + "$ref": "#/definitions/Files" + } + }, + "required": [ + "Options" + ], + "additionalProperties": false, + "type": "object" + } + }, + "required": [ + "KeymanDeveloperProject" + ], + "additionalProperties": false, + "definitions": { + "Options": { + "type": "object", + "properties": { + "BuildPath": { + "type": "string" + }, + "SourcePath": { + "type": "string" + }, + "CompilerWarningsAsErrors": { + "type": "string", + "pattern": "^(True|False)$" + }, + "WarnDeprecatedCode": { + "type": "string", + "pattern": "^(True|False)$" + }, + "CheckFilenameConventions": { + "type": "string", + "pattern": "^(True|False)$" + }, + "ProjectType": { + "type": "string", + "pattern": "^(keyboard|lexicalmodel)$" + }, + "Version": { + "type": "string", + "pattern": "^(1\\.0|2\\.0)$" + } + }, + "required": [ + ], + "additionalProperties": false + }, + "Files": { + "type": "object", + "properties": { + "File": { + "type": "array", + "items": { + "$ref": "#/definitions/File" + } + } + }, + "additionalProperties": false, + "required": [ + "File" + ] + }, + "File": { + "type": "object", + "properties": { + "ID": { + "type": "string" + }, + "Filename": { + "type": "string" + }, + "Filepath": { + "type": "string" + }, + "FileVersion": { + "type": "string" + }, + "FileType": { + "type": "string" + }, + "Details": { + "$ref": "#/definitions/FileDetails" + }, + "ParentFileID": { + "type": "string" + } + }, + "required": [ + "Filename" + ], + "additionalProperties": false + }, + "FileDetails": { + "type": "object", + "properties": { + "Name": { + "type": "string" + }, + "Copyright": { + "type": "string" + }, + "Message": { + "type": "string" + }, + "Version": { + "type": "string" + } + }, + "required": [ + ], + "additionalProperties": false + } + } +} \ No newline at end of file diff --git a/common/schemas/kvk/kvk.ksy b/common/schemas/kvk/kvk.ksy new file mode 100644 index 00000000000..11d8f2faa77 --- /dev/null +++ b/common/schemas/kvk/kvk.ksy @@ -0,0 +1,122 @@ +meta: + id: kvk + title: Keyman Visual Keyboard + file-extension: kvk + license: MIT + ks-version: 0.9 + endian: le + bit-endian: le +doc: | + KVK is the binary file format for Keyman Visual Keyboard + files. KVKS is the equivalent XML source file format +doc-ref: + - https://github.com/keymanapp/keyman/ +seq: + - id: header + type: header + - id: keys + type: keys +types: + header: + seq: + - id: identifier + contents: 'KVKF' + doc: Magic file identifier, always KVKF + - id: version + contents: [0, 6, 0, 0] + doc: Version number of KVK file format, always 0x00000600 + - id: flag + type: header_flags + - id: associated_keyboard + type: string + - id: ansi_font + type: font + - id: unicode_font + type: font + + header_flags: + seq: + - id: display_102 + type: b1 + doc: kvkh102, Keyboard should display 102nd key + - id: display_underlying + type: b1 + doc: kvkhDisplayUnderlying, Keyboard should display underlying characters + - id: use_underlying + type: b1 + doc: kvkhUseUnderlying, + - id: altgr + type: b1 + doc: kvkhAltGr, Keyboard should treat left/right Ctrl and Alt separately + + keys: + seq: + - id: count + type: u4 + - id: key + type: key + repeat: expr + repeat-expr: count + key: + seq: + - id: flags + type: key_flags + - id: modifiers + type: key_modifiers + - id: vkey + type: u2 + - id: text + type: string + - id: bitmap + type: u4 + + key_modifiers: + seq: + - id: shift + type: b1 + - id: ctrl + type: b1 + - id: alt + type: b1 + - id: lctrl + type: b1 + - id: rctrl + type: b1 + - id: lalt + type: b1 + - id: ralt + type: b1 + - id: padding + type: b1 + doc: reserved, + - id: zeropad + contents: [0] + + key_flags: + seq: + - id: bitmap + type: b1 + - id: unicode + type: b1 + - id: padding + type: b6 + + font: + seq: + - id: name + type: string + - id: size + type: u4 + - id: color + type: u4 + + string: + seq: + - id: len + type: u2 + - id: str + type: str + size: len*2 - 2 + encoding: utf-16 + - id: zero_terminator + contents: [0,0] diff --git a/common/schemas/kvks/README.md b/common/schemas/kvks/README.md new file mode 100644 index 00000000000..fb4f2a8a687 --- /dev/null +++ b/common/schemas/kvks/README.md @@ -0,0 +1,14 @@ +# .kvks schema + +This schema validates .kvks files, according to the reference implementation +from VisualKeyboardLoaderXML.pas. + +## Notes on conversion from xsd to json-schema + +Converted using xsd2json. Following structural changes: + +* kvk-version base type from km-version to string, copy km-version pattern in +* remove xs:all bracketing +* remove format:double from fontsize, change type to string +* encoding property changed type to array +* kvk-key added _ property for base text value \ No newline at end of file diff --git a/common/schemas/kvks/kvks.schema.json b/common/schemas/kvks/kvks.schema.json new file mode 100644 index 00000000000..357b28b6022 --- /dev/null +++ b/common/schemas/kvks/kvks.schema.json @@ -0,0 +1,182 @@ +{ + "title": "kvks.xsd", + "$schema": "http://json-schema.org/schema#", + "type": "object", + "properties": { + "visualkeyboard": { + "properties": { + "header": { + "$ref": "#/definitions/kvk-header" + }, + "encoding": { + "type": "array", + "items": { + "$ref": "#/definitions/kvk-encoding" + } + } + }, + "required": [ + "header" + ], + "additionalProperties": false, + "type": "object" + } + }, + "required": [ + "visualkeyboard" + ], + "additionalProperties": false, + "definitions": { + "kvk-header": { + "type": "object", + "properties": { + "version": { + "$ref": "#/definitions/kvk-version" + }, + "kbdname": { + "type": "string" + }, + "flags": { + "$ref": "#/definitions/kvk-header-flags" + }, + "layout": { + "type": "string" + } + }, + "required": [ + "version" + ], + "additionalProperties": false + }, + "kvk-header-flags": { + "type": "object", + "properties": { + "key102": { + "$ref": "#/definitions/km-empty" + }, + "displayunderlying": { + "$ref": "#/definitions/km-empty" + }, + "usealtgr": { + "$ref": "#/definitions/km-empty" + }, + "useunderlying": { + "$ref": "#/definitions/km-empty" + } + }, + "additionalProperties": false + }, + "kvk-encoding": { + "type": "object", + "properties": { + "layer": { + "type": "array", + "items": { + "$ref": "#/definitions/kvk-layer" + } + }, + "$": { + "type": "object", + "properties": { + "name": { + "$ref": "#/definitions/kvk-encoding-name" + }, + "fontname": { + "type": "string" + }, + "fontsize": { + "type": "string" + } + }, + "required": [ + "name" + ], + "additionalProperties": false + } + }, + "required": [ + "$" + ], + "additionalProperties": false + }, + "kvk-layer": { + "type": "object", + "properties": { + "key": { + "type": "array", + "items": { + "$ref": "#/definitions/kvk-key" + } + }, + "$": { + "type": "object", + "properties": { + "shift": { + "$ref": "#/definitions/kvk-layer-shift" + } + }, + "required": [ + "shift" + ], + "additionalProperties": false + } + }, + "required": [ + "$" + ], + "additionalProperties": false + }, + "kvk-key": { + "type": "object", + "properties": { + "bitmap": { + "type": "string" + }, + "$": { + "type": "object", + "properties": { + "vkey": { + "type": "string" + } + }, + "required": [ + "vkey" + ], + "additionalProperties": false + }, + "_": { + "type": "string" + } + }, + "required": [ + "$" + ], + "additionalProperties": false + }, + "km-empty": { + "type": "string" + }, + "kvk-encoding-name": { + "type": "string", + "enum": [ + "ansi", + "unicode" + ] + }, + "kvk-layer-shift": { + "type": "string", + "pattern": "S?(C|LC|RC)?(A|LA|RA)?" + }, + "kvk-version": { + "type": "string", + "pattern": "(\\d+\\.)+(\\d+)", + "enum": [ + "10.0" + ] + }, + "km-version": { + "type": "string", + "pattern": "(\\d+\\.)+(\\d+)" + } + } +} \ No newline at end of file diff --git a/common/schemas/kvks/kvks.xsd b/common/schemas/kvks/kvks.xsd new file mode 100644 index 00000000000..17056a5a57b --- /dev/null +++ b/common/schemas/kvks/kvks.xsd @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/common/tools/hextobin/.gitignore b/common/tools/hextobin/.gitignore new file mode 100644 index 00000000000..567609b1234 --- /dev/null +++ b/common/tools/hextobin/.gitignore @@ -0,0 +1 @@ +build/ diff --git a/common/tools/hextobin/build.sh b/common/tools/hextobin/build.sh new file mode 100755 index 00000000000..702e6610f01 --- /dev/null +++ b/common/tools/hextobin/build.sh @@ -0,0 +1,42 @@ +#!/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]}")" +. "${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 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 + diff --git a/common/tools/hextobin/hextobin.ts b/common/tools/hextobin/hextobin.ts new file mode 100644 index 00000000000..7a184f95fb7 --- /dev/null +++ b/common/tools/hextobin/hextobin.ts @@ -0,0 +1,47 @@ +#!/usr/bin/env node +import * as fs from 'fs'; +import * as rd from 'readline'; +import * as program from 'commander'; + +let inputFilename: string = ""; +let outputFilename: string = ""; + +program + .description( +`Will convert the input file which is a hex dump to the output file. Hex dump can contain +blank lines, offsets, commands and comments. File writing will start at zero, padding is +not currently supported. + +Format: + + # This is a comment + block(name) # each part of file must be defined as a block + 11 22 33 44 55 aa bb # inserts hex bytes + offset(block) # inserts 4 byte offset of block from BOF + sizeof(block[,divisor]) # inserts 4 byte size of block, optionally divided by divisor + diff(block1,block2) # inserts 4 byte offset diff between start of block1 and block2 + index(block1,block2[,divisor]) # inserts 4 byte index diff between block1 and block2, + # optionally divided by divisor + # block names are required, but if not referenced can be reused (e.g. block(x)) +` + ) + .arguments('') + .arguments('') + .action((infile:any, outfile:any) => { inputFilename = infile; outputFilename = outfile; }); + +program.parse(process.argv); + +if(!inputFilename || !outputFilename) { + exitDueToUsageError('Must provide an input and output filename.'); +} + +function exitDueToUsageError(message: string): never { + console.error(`${program._name}: ${message}`); + console.error(); + program.outputHelp(); + return process.exit(64); // SysExits.EX_USAGE +} + +import hextobin from './index'; + +hextobin(inputFilename, outputFilename, {silent: false}); diff --git a/common/tools/hextobin/index.ts b/common/tools/hextobin/index.ts new file mode 100644 index 00000000000..db209453839 --- /dev/null +++ b/common/tools/hextobin/index.ts @@ -0,0 +1,229 @@ +import * as fs from 'fs'; +import * as rd from 'readline'; + +export default async function hextobin(inputFilename: string, outputFilename?: string, options?: {silent?: boolean}): Promise { + + options = {...options}; + options.silent = !!options.silent; + + let reader = rd.createInterface(fs.createReadStream(inputFilename)); + + type HexBlockRefType = + 'sizeof' | // gets the size of a block, optionally divided by divisor: sizeof(block,divisor) + 'offset' | // gets the offset of a block in bytes from start of file: offset(block) + 'diff' | // gets the offset of a block in bytes from start of a base block: offset(baseBlock,block) + 'index'; // gets the index of a block from a base block, optionally divided by divisor: index(baseBlock,block,divisor) + + interface HexBlockRef { + type: HexBlockRefType; + blockName: string; + blockName2?: string; // used only by diff, index + divisor?: number; // used only by sizeof, index + offset: number; // actual byte offset in hex data (i.e. offset in string is * 2) + }; + + interface HexBlock { + name: string; + offset: number; // calculated during reconciliation phase + hex: string; + refs: HexBlockRef[]; + }; + + let blocks: HexBlock[] = []; + + let currentLine = ''; + let currentLineNumber = 0; + + if(!await load()) { + return null; + } + + if(!reconciliation()) { + return null; + } + + return save(); + + function reportError(message: string) { + if(!options.silent) { + console.error(`Invalid input file: ${message} on line #${currentLineNumber}: "${currentLine}"`); + } + } + + function currentBlock(): HexBlock { + return blocks.length ? blocks[blocks.length-1] : null; + } + + function parseToken(token: string): { command: string, parameters: string[] } { + let m = /^([a-z]+)\((.+)\)$/.exec(token); + if(!m) { + if(!token.match(/^[a-fA-F0-9]{2}$/)) { + return null; + } + // hex byte + return { command: 'data', parameters: [token] }; + } + + // processing command + return { command: m[1], parameters: m[2].split(',') }; + } + + function token(token: string) { + const t = parseToken(token); + if(!t) { + reportError(`expected command or hex at token "${token}"`); + return false; + } + + if(t.command == 'block') { + blocks.push({name: t.parameters[0], offset: 0, hex: '', refs: []}); + return true; + } + + const b = currentBlock(); + if(!b) { + reportError(`expected block() before data`); + return false; + } + + switch(t.command) { + case 'offset': + b.refs.push({type: 'offset', blockName: t.parameters[0], offset: b.hex.length / 2}); + b.hex += "_".repeat(8); // always 4 bytes, placeholder will be filled in during reconciliation phase + break; + case 'sizeof': + // sizeof can take a second parameter, divisor + { + let divisor = t.parameters.length > 1 ? parseInt(t.parameters[1],10) : 1; + b.refs.push({type: 'sizeof', blockName: t.parameters[0], divisor: divisor, offset: b.hex.length / 2}); + b.hex += "_".repeat(8); // always 4 bytes, placeholder will be filled in during reconciliation phase + } + break; + case 'diff': + b.refs.push({type: 'diff', blockName: t.parameters[0], blockName2: t.parameters[1], offset: b.hex.length / 2}); + b.hex += "_".repeat(8); // always 4 bytes, placeholder will be filled in during reconciliation phase + break; + case 'index': + // index can take a third parameter, divisor + { + let divisor = t.parameters.length > 2 ? parseInt(t.parameters[2],10) : 1; + b.refs.push({type: 'index', blockName: t.parameters[0], blockName2: t.parameters[1], divisor: divisor, offset: b.hex.length / 2}); + b.hex += "_".repeat(8); // always 4 bytes, placeholder will be filled in during reconciliation phase + } + break; + case 'data': + b.hex += t.parameters[0]; + break; + default: + reportError(`unknown command ${t.command}`); + return false; + } + return true; + } + + async function load() { + for await (const l of reader) { + // Error reporting variables + currentLine = l; + currentLineNumber++; + + const tokens = l.split(/[ \t]+/); + for(let t of tokens) { + if(t.startsWith('#')) { + // comment, ignore all subsequent tokens to EOL + break; + } + if(t == '') { + continue; + } + if(!token(t)) { + return false; + } + } + } + return true; + } + + function dwordLeToHex(v: number): string { + // hacky but who cares + let h = v.toString(16); + h = "0".repeat(8-h.length) + h; + return h.substring(6,8) + h.substring(4, 6) + h.substring(2, 4) + h.substring(0, 2); + } + + function fillBlockPlaceholder(block: HexBlock, offset: number, value: number): void { + const hexvalue = dwordLeToHex(value); + block.hex = block.hex.substring(0, offset * 2) + hexvalue + block.hex.substring(offset * 2 + 8); + } + + function reconciliation(): boolean { + let offset = 0; + + // calculate block sizes + for(let b of blocks) { + b.offset = offset; + offset += b.hex.length / 2; + } + + // reconcile block offsets and sizes + for(let b of blocks) { + for(let r of b.refs) { + const v = blocks.find(q => q.name == r.blockName); + if(!v) { + reportError(`Could not find block ${r.blockName} when reconciling ${b.name}`); + return false; + } + switch(r.type) { + case 'diff': + { + const v2 = blocks.find(q => q.name == r.blockName2); + if(!v2) { + reportError(`Could not find block ${r.blockName2} when reconciling ${b.name}`); + return false; + } + fillBlockPlaceholder(b, r.offset, v2.offset - v.offset); + } + break; + case 'offset': + fillBlockPlaceholder(b, r.offset, v.offset); + break; + case 'sizeof': + fillBlockPlaceholder(b, r.offset, v.hex.length / 2 / r.divisor); + break; + case 'index': + { + const v2 = blocks.find(q => q.name == r.blockName2); + if(!v2) { + reportError(`Could not find block ${r.blockName2} when reconciling ${b.name}`); + return false; + } + fillBlockPlaceholder(b, r.offset, (blocks.indexOf(v2) - blocks.indexOf(v)) / r.divisor); + } + break; + default: + reportError(`Invalid ref ${r.type}`); + return false; + } + } + } + return true; + } + + function save(): Uint8Array { + let total = blocks.reduce((total: number, item: HexBlock) => Math.max(item.offset + item.hex.length/2, total), 0); + + if(!options.silent) { + console.log(`${currentLineNumber} lines read; ${blocks.length} sections to write. Total file size = ${total} bytes.` ); + } + let buffer = new Uint8Array(total); + blocks.forEach(item => { + let buf = Buffer.from(item.hex, 'hex'); + buffer.set(buf, item.offset); + }); + + if(outputFilename) { + fs.writeFileSync(outputFilename, buffer); + } + return buffer; + } +} \ No newline at end of file diff --git a/common/tools/hextobin/package.json b/common/tools/hextobin/package.json new file mode 100644 index 00000000000..155f8297597 --- /dev/null +++ b/common/tools/hextobin/package.json @@ -0,0 +1,28 @@ +{ + "name": "@keymanapp/hextobin", + "description": "hextobin conversion tool", + "keywords": [ + "keyman", + "hex", + "binary" + ], + "license": "MIT", + "dependencies": { + "commander": "^5.1.0" + }, + "main": "build/index.js", + "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 new file mode 100644 index 00000000000..e32831fe737 --- /dev/null +++ b/common/tools/hextobin/tsconfig.json @@ -0,0 +1,19 @@ +{ + "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" + ] +} diff --git a/common/web/input-processor/build.sh b/common/web/input-processor/build.sh index 7f36416fcd4..03e012aa196 100755 --- a/common/web/input-processor/build.sh +++ b/common/web/input-processor/build.sh @@ -36,7 +36,7 @@ builder_describe_outputs \ configure:module /node_modules \ configure:tools /node_modules \ build:module build/index.js \ - build:tools /developer/src/kmlmc/dist/kmlmc.js # TODO: remove this once kmlmc is a dependency + build:tools /developer/src/kmc/build/src/kmlmc.js # TODO: remove this once kmlmc is a dependency builder_parse "$@" @@ -59,8 +59,11 @@ fi if builder_start_action build:tools; then # Used by test:module # TODO: convert to a dependency once we have updated kmlmc to use builder script - pushd "$KEYMAN_ROOT/developer/src/kmlmc" - ./build.sh -S + pushd "$KEYMAN_ROOT/developer/src/kmc-model" + ./build.sh + popd + pushd "$KEYMAN_ROOT/developer/src/kmc" + ./build.sh popd builder_finish_action success build:tools diff --git a/common/web/input-processor/package.json b/common/web/input-processor/package.json index c83090ee862..6675574334d 100644 --- a/common/web/input-processor/package.json +++ b/common/web/input-processor/package.json @@ -17,7 +17,7 @@ }, "homepage": "https://github.com/keymanapp/keyman#readme", "devDependencies": { - "@keymanapp/lexical-model-compiler": "*", + "@keymanapp/kmc": "*", "@keymanapp/resources-gosh": "*", "@types/node": "^11.9.4", "chai": "^4.3.4", diff --git a/common/web/input-processor/tests/cases/languageProcessor.js b/common/web/input-processor/tests/cases/languageProcessor.js index 13d98398119..c3e02a97621 100644 --- a/common/web/input-processor/tests/cases/languageProcessor.js +++ b/common/web/input-processor/tests/cases/languageProcessor.js @@ -6,7 +6,8 @@ var vm = require("vm"); * Unit tests for the Dummy prediction model. */ -var LexicalModelCompiler = require('../../../../../developer/src/kmlmc/dist/lexical-model-compiler/lexical-model-compiler').default; +// TODO: this relies on esbuild output for lexical-model-compiler; later should use import +var LexicalModelCompiler = require('../../../../../developer/src/kmc-model/build/cjs-src/lexical-model-compiler.cjs').default; var path = require('path'); let InputProcessor = require('../../build/index.bundled.js'); @@ -49,7 +50,7 @@ describe('LanguageProcessor', function() { describe('.predict', function() { let compiler = new LexicalModelCompiler(); const MODEL_ID = 'example.qaa.trivial'; - const PATH = path.join(__dirname, '../../../../../developer/src/kmlmc/tests/fixtures', MODEL_ID); + const PATH = path.join(__dirname, '../../../../../developer/src/kmc-model/test/fixtures', MODEL_ID); describe('using angle brackets for quotes', function() { let modelCode = compiler.generateLexicalModelCode(MODEL_ID, { diff --git a/common/web/keyman-version/.gitignore b/common/web/keyman-version/.gitignore index 3e3464001b7..7cedcbd35dd 100644 --- a/common/web/keyman-version/.gitignore +++ b/common/web/keyman-version/.gitignore @@ -1,2 +1,5 @@ # version.inc.ts is generated during the build -version.inc.ts \ No newline at end of file +version.inc.ts + +# keyman-version.mts (esm clone of version.inc.ts) is generated during the build +keyman-version.mts \ No newline at end of file diff --git a/common/web/keyman-version/build.sh b/common/web/keyman-version/build.sh index 4f713b7a403..718e2ed9540 100755 --- a/common/web/keyman-version/build.sh +++ b/common/web/keyman-version/build.sh @@ -28,7 +28,7 @@ builder_describe "Build the include script for current Keyman version" \ builder_describe_outputs \ configure "/node_modules" \ - build "build/index.js" + build "build/keyman-version.mjs" builder_parse "$@" @@ -45,7 +45,24 @@ if builder_start_action clean; then fi if builder_start_action build; then - # Generate index.ts + # Generate keyman-version.mts + echo " + // Generated by common/web/keyman-version/build.sh + export default class KEYMAN_VERSION { + static readonly VERSION = \"$VERSION\"; + static readonly VERSION_RELEASE =\"$VERSION_RELEASE\"; + static readonly VERSION_MAJOR = \"$VERSION_MAJOR\"; + static readonly VERSION_MINOR = \"$VERSION_MINOR\"; + static readonly VERSION_PATCH = \"$VERSION_PATCH\"; + static readonly TIER =\"$TIER\"; + static readonly VERSION_TAG = \"$VERSION_TAG\"; + static readonly VERSION_WITH_TAG = \"$VERSION_WITH_TAG\"; + static readonly VERSION_ENVIRONMENT = \"$VERSION_ENVIRONMENT\"; + static readonly SENTRY_RELEASE = \"release-$VERSION_WITH_TAG\"; + } + " > ./keyman-version.mts + + # Generate version.inc.ts -- used by TypeScript code that isn't yet modular echo " // Generated by common/web/keyman-version/build.sh namespace com.keyman { diff --git a/common/web/keyman-version/package.json b/common/web/keyman-version/package.json index 4d300e8dc24..cbd6db8dd3a 100644 --- a/common/web/keyman-version/package.json +++ b/common/web/keyman-version/package.json @@ -2,13 +2,17 @@ "name": "@keymanapp/keyman-version", "description": "Keyman global version data", "main": "./build/index.js", - "exports": "./build/index.js", + "exports": { + ".": "./build/index.js", + "./keyman-version.mjs": "./build/keyman-version.mjs" + }, "scripts": { "build": "echo 'Building @keymanapp/keyman-version' && tsc -b", "clean": "tsc -b --clean" }, "license": "MIT", "devDependencies": { + "@types/node": "^18.7.13", "typescript": "^4.9.5" } } diff --git a/common/web/keyman-version/tsconfig.esm.json b/common/web/keyman-version/tsconfig.esm.json new file mode 100644 index 00000000000..1e722698730 --- /dev/null +++ b/common/web/keyman-version/tsconfig.esm.json @@ -0,0 +1,18 @@ +{ + "extends": "../../../tsconfig-base.json", + + "compilerOptions": { + "allowJs": false, + "declaration": true, + "module": "es2020", + "outDir": "./build/", + "baseUrl": ".", + "rootDir": ".", + "sourceMap": true, + "lib": ["es6"], + "target": "es6", + }, + "include": [ + "keyman-version.mts" + ] +} diff --git a/common/web/types/build.sh b/common/web/types/build.sh new file mode 100755 index 00000000000..dc376f4a0d2 --- /dev/null +++ b/common/web/types/build.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# +# Compiles the common file types module +# + +# 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" + +cd "$THIS_SCRIPT_PATH" + +builder_describe "Build Keyman common file types module" \ + "@../keyman-version" \ + "configure" \ + "build" \ + "clean" \ + "test" \ + "publish publish to npm" \ + "--dry-run,-n don't actually publish, just dry run" +builder_describe_outputs \ + configure /node_modules \ + build build/src/main.js + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action clean; then + rm -rf ./build/ ./tsconfig.tsbuildinfo + builder_finish_action success clean +else + # We need the schema files at runtime and bundled, so always copy it for all actions except `clean` + mkdir -p "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json" "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json" "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/common/schemas/kvks/kvks.schema.json" "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/common/schemas/kpj/kpj.schema.json" "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json" "$THIS_SCRIPT_PATH/build/src/" + # Store CLDR imports + # load all versions that have a cldr_info.json + for CLDR_INFO_PATH in "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/"*/cldr_info.json + do + # TODO-LDML: developer/src/inst/download.in.mak needs these also... + CLDR_PATH=$(dirname "$CLDR_INFO_PATH") + CLDR_VER=$(basename "$CLDR_PATH") + mkdir -p "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER" + # TODO-LDML: When these are copied, the DOCTYPE will break due to the wrong path. We don't use the DTD so it should be OK. + cp "$CLDR_INFO_PATH" "$CLDR_PATH/import/"*.xml "$THIS_SCRIPT_PATH/build/src/import/$CLDR_VER/" + done +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 + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action test; then + npm test + builder_finish_action success test +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action publish; then + . "$KEYMAN_ROOT/resources/build/npm-publish.inc.sh" + npm_publish + builder_finish_action success publish +fi diff --git a/common/web/types/package.json b/common/web/types/package.json new file mode 100644 index 00000000000..c192de57490 --- /dev/null +++ b/common/web/types/package.json @@ -0,0 +1,57 @@ +{ + "name": "@keymanapp/common-types", + "description": "Keyman Developer keyboard file types", + "keywords": [ + "keyboard", + "keyman", + "ldml", + "unicode" + ], + "type": "module", + "exports": { + ".": "./build/src/main.js" + }, + "scripts": { + "build": "tsc -b", + "test": "cd test && tsc -b && cd .. && c8 --skip-full --reporter=lcov --reporter=text mocha", + "prepublishOnly": "npm run build" + }, + "author": "Marc Durdin (https://github.com/mcdurdin)", + "license": "MIT", + "bugs": { + "url": "https://github.com/keymanapp/keyman/issues" + }, + "dependencies": { + "@keymanapp/keyman-version": "*", + "ajv": "^8.11.0", + "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", + "semver": "^7.3.7", + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/git-diff": "^2.0.3", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "@types/semver": "^7.3.12", + "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "git-diff": "^2.0.6", + "hexy": "^0.3.4", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "4.6.3" + }, + "mocha": { + "spec": "build/test/**/test-*.js", + "require": [ + "source-map-support/register" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" + } +} diff --git a/common/web/types/src/consts/virtual-key-constants.ts b/common/web/types/src/consts/virtual-key-constants.ts new file mode 100644 index 00000000000..199060312e4 --- /dev/null +++ b/common/web/types/src/consts/virtual-key-constants.ts @@ -0,0 +1,231 @@ + +// Define standard keycode numbers (exposed for use by other modules) +// TODO-LDML: merge with common\web\keyboard-processor\src\text\codes.ts + +/** + * May include non-US virtual key codes + */ +export const USVirtualKeyCodes = { + K_BKSP:8, + K_TAB:9, + K_ENTER:13, + K_SHIFT:16, + K_CONTROL:17, + K_ALT:18, + K_PAUSE:19, + K_CAPS:20, + K_ESC:27, + K_SPACE:32, + K_PGUP:33, + K_PGDN:34, + K_END:35, + K_HOME:36, + K_LEFT:37, + K_UP:38, + K_RIGHT:39, + K_DOWN:40, + K_SEL:41, + K_PRINT:42, + K_EXEC:43, + K_INS:45, + K_DEL:46, + K_HELP:47, + K_0:48, + K_1:49, + K_2:50, + K_3:51, + K_4:52, + K_5:53, + K_6:54, + K_7:55, + K_8:56, + K_9:57, + K_A:65, + K_B:66, + K_C:67, + K_D:68, + K_E:69, + K_F:70, + K_G:71, + K_H:72, + K_I:73, + K_J:74, + K_K:75, + K_L:76, + K_M:77, + K_N:78, + K_O:79, + K_P:80, + K_Q:81, + K_R:82, + K_S:83, + K_T:84, + K_U:85, + K_V:86, + K_W:87, + K_X:88, + K_Y:89, + K_Z:90, + K_NP0:96, + K_NP1:97, + K_NP2:98, + K_NP3:99, + K_NP4:100, + K_NP5:101, + K_NP6:102, + K_NP7:103, + K_NP8:104, + K_NP9:105, + K_NPSTAR:106, + K_NPPLUS:107, + K_SEPARATOR:108, + K_NPMINUS:109, + K_NPDOT:110, + K_NPSLASH:111, + K_F1:112, + K_F2:113, + K_F3:114, + K_F4:115, + K_F5:116, + K_F6:117, + K_F7:118, + K_F8:119, + K_F9:120, + K_F10:121, + K_F11:122, + K_F12:123, + K_NUMLOCK:144, + K_SCROLL:145, + K_LSHIFT:160, + K_RSHIFT:161, + K_LCONTROL:162, + K_RCONTROL:163, + K_LALT:164, + K_RALT:165, + K_COLON:186, + K_EQUAL:187, + K_COMMA:188, + K_HYPHEN:189, + K_PERIOD:190, + K_SLASH:191, + K_BKQUOTE:192, + K_LBRKT:219, + K_BKSLASH:220, + K_RBRKT:221, + K_QUOTE:222, + K_oE2:226, // ISO B00, key to right of left shift, not on US keyboard + K_OE2:226, + k_oC1:193, // ISO B11, ABNT-2 key to left of right shift, not on US keyboard + k_OC1:193, + /*K_LOPT:50001, + K_ROPT:50002, + K_NUMERALS:50003, + K_SYMBOLS:50004, + K_CURRENCIES:50005, + K_UPPER:50006, + K_LOWER:50007, + K_ALPHA:50008, + K_SHIFTED:50009, + K_ALTGR:50010, + K_TABBACK:50011, + K_TABFWD:50012*/ +}; + +const k = USVirtualKeyCodes; + +export type KeyMap = number[][]; + +export const USVirtualKeyMap: 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 ], + // [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 ], + // [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 ], + // [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_BKSLASH ], + // [shift] * Z X C V B N M , . / [shift] *=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 ], + // space + [ k.K_SPACE ], +]; + +/** + * Map from a hardware constant to a keymap + * For the 'key' see constants.layr_list_hardware_map + */ +export const HardwareToKeymap: Map = new Map( + [ + ["us", USVirtualKeyMap], + ["iso", ISOVirtualKeyMap], + //TODO-LDML: jis #8161 + //TODO-LDML: abnt2 #8161 + ] +); + +/** + * 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/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts new file mode 100644 index 00000000000..eb543254248 --- /dev/null +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-reader.ts @@ -0,0 +1,80 @@ +import Ajv from "ajv"; +import { TouchLayoutFile } from "./keyman-touch-layout-file.js"; + +export class TouchLayoutFileReader { + public read(source: Uint8Array): TouchLayoutFile { + const decoder = new TextDecoder('utf-8', {fatal:true, ignoreBOM: true}); + let sourceString: string; + try { + sourceString = decoder.decode(source); + /* c8 ignore next 7 */ + } catch(e) { + if(e instanceof TypeError) { + // TODO: Do we want to do something else with this? + throw e; + } + throw e; + } + + let result: TouchLayoutFile; + try { + result = JSON.parse(sourceString, function(key, value) { + // `row.id` should be number, but may have been stringified; we use + // presence of `key` property to recognise this as a `TouchLayoutRow`. + if(this.key && key == 'id' && typeof value == 'string') { + let newValue = parseInt(value, 10); + /* c8 ignore next 3 */ + if(isNaN(newValue)) { + throw new TypeError(`Invalid row.id: "${value}"`); + } + return newValue; + } + + // `key.width`, `key.pad`, `key.sp` should be number, but may have been + // stringified + if(key == 'width' || key == 'pad' || key == 'sp') { + if(value === '') { + // Empty string is equivalent to not present, so fall back to + // default value + return undefined; + } + + let newValue = parseInt(value, 10); + /* c8 ignore next 3 */ + if(isNaN(newValue)) { + throw new TypeError(`Invalid [sub]key.${key}: "${value}"`); + } + return newValue; + } + + if(Array.isArray(value) && value.length == 0) { + // Delete empty arrays + return undefined; + } + + return value; + }); + /* c8 ignore next 7 */ + } catch(e) { + if(e instanceof SyntaxError) { + // TODO: Do we want to do something else with this? + throw e; + } + throw e; + } + + return result; + } + + public validate(source: TouchLayoutFile, schemaBuffer: Buffer): void { + const schema = JSON.parse(schemaBuffer.toString('utf8')); + const ajv = new Ajv(); + if(!ajv.validate(schema, source)) + /* c8 ignore next 3 */ + { + throw new Error(ajv.errorsText()); + } + } + + +}; \ No newline at end of file diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-writer.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-writer.ts new file mode 100644 index 00000000000..a86ed0441f7 --- /dev/null +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file-writer.ts @@ -0,0 +1,84 @@ +import { TouchLayoutFile, TouchLayoutPlatform, TouchLayoutKey, TouchLayoutSubKey } from "./keyman-touch-layout-file.js"; + +export interface TouchLayoutFileWriterOptions { + formatted?: boolean; +}; + +export class TouchLayoutFileWriter { + + options: TouchLayoutFileWriterOptions; + + constructor(options?: TouchLayoutFileWriterOptions) { + this.options = {...options}; + } + + /** + * Writes the touch layout to a .keyman-touch-layout JSON file + * @param source TouchLayoutFile + * @returns Uint8Array, the .keyman-touch-layout file + */ + write(source: TouchLayoutFile): Uint8Array { + const output = JSON.stringify(source, null, this.options?.formatted ? 2 : undefined); + const encoder = new TextEncoder(); + return encoder.encode(output); + } + + /** + * Compiles the touch layout file into a KeymanWeb-compatible JSON-style + * object string. In the future, this may be optimized to remove unnecessary + * quoting of property names, and remove unused properties, as this string + * is embedded into .js code. + * @param source + * @returns string + */ + compile(source: TouchLayoutFile): string { + // Deep copy the source + source = JSON.parse(JSON.stringify(source)); + + // Fixup pad, width and sp to string types, as that's what KeymanWeb + // currently expects + + const fixupKey = (key: TouchLayoutKey | TouchLayoutSubKey) => { + if(Object.hasOwn(key, 'pad')) (key.pad as any) = key.pad.toString(); + if(Object.hasOwn(key, 'sp')) (key.sp as any) = key.sp.toString(); + if(Object.hasOwn(key, 'width')) (key.width as any) = key.width.toString(); + }; + + const fixupPlatform = (platform: TouchLayoutPlatform) => { + for(let layer of platform.layer) { + for(let row of layer.row) { + for(let key of row.key) { + fixupKey(key); + if(key.sk) { + for(let sk of key.sk) { + fixupKey(sk); + } + } + if(key.multitap) { + for(let sk of key.multitap) { + fixupKey(sk); + } + } + if(key.flick) { + for(let id of Object.keys(key.flick)) { + fixupKey((key.flick as any)[id] as TouchLayoutSubKey); + } + } + } + } + } + }; + + if(source.desktop) { + fixupPlatform(source.desktop); + } + if(source.phone) { + fixupPlatform(source.phone); + } + if(source.tablet) { + fixupPlatform(source.tablet); + } + + return JSON.stringify(source, null, this.options?.formatted ? 2 : undefined); + } +}; \ No newline at end of file diff --git a/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts new file mode 100644 index 00000000000..564bc12b6ee --- /dev/null +++ b/common/web/types/src/keyman-touch-layout/keyman-touch-layout-file.ts @@ -0,0 +1,87 @@ +// +// .keyman-touch-layout JSON format +// +// Follows /common/schemas/keyman-touch-layout/keyman-touch-layout.spec.json for +// reading and +// /common/schemas/keyman-touch-layout/keyman-touch-layout.clean.spec.json for +// writing +// + +export interface TouchLayoutFile { + tablet?: TouchLayoutPlatform; + phone?: TouchLayoutPlatform; + desktop?: TouchLayoutPlatform; +}; + +export type TouchLayoutFont = string; +export type TouchLayoutFontSize = string; +export type TouchLayoutDefaultHint = "none"|"dot"|"longpress"|"multitap"|"flick"|"flick-n"|"flick-ne"|"flick-e"|"flick-se"|"flick-s"|"flick-sw"|"flick-w"|"flick-nw"; + +export interface TouchLayoutPlatform { + font?: TouchLayoutFont; + fontsize?: TouchLayoutFontSize; + layer: TouchLayoutLayer[]; + displayUnderlying?: boolean; + defaultHint: TouchLayoutDefaultHint; +}; + +export type TouchLayoutLayerId = string; // pattern = /^[a-zA-Z0-9_-]+$/ + +export interface TouchLayoutLayer { + id: TouchLayoutLayerId; + row: TouchLayoutRow[]; +}; + +export type TouchLayoutRowId = number; + +export interface TouchLayoutRow { + id: TouchLayoutRowId; + key: TouchLayoutKey[]; +}; + +type Key_Type = 'T'|'K'|'U'|'t'|'k'|'u'; +type Key_Id = string; +export type TouchLayoutKeyId = `${Key_Type}_${Key_Id}`; // pattern = /^[TKUtku]_[a-zA-Z0-9_]+$/ + +export interface TouchLayoutKey { + id?: TouchLayoutKeyId; + text?: string; + layer?: TouchLayoutLayerId; + nextlayer?: TouchLayoutLayerId; + font?: TouchLayoutFont; + fontsize?: TouchLayoutFontSize; + sp?: TouchLayoutKeySp; + pad?: TouchLayoutKeyPad; + width?: TouchLayoutKeyWidth; + sk?: TouchLayoutSubKey[]; + flick?: TouchLayoutFlick; + multitap?: TouchLayoutSubKey[]; + hint?: string; +}; + +export enum TouchLayoutKeySp { normal=0, special=1, specialActive=2, deadkey=8, blank=9, spacer=10 }; +export type TouchLayoutKeyPad = number; // 0-100000 +export type TouchLayoutKeyWidth = number; // 0-100000 + +export interface TouchLayoutSubKey { + id: TouchLayoutKeyId; + text?: string; + layer?: TouchLayoutLayerId; + nextlayer?: TouchLayoutLayerId; + font?: TouchLayoutFont; + fontsize?: TouchLayoutFontSize; + sp?: TouchLayoutKeySp; + pad?: TouchLayoutKeyPad; + width?: TouchLayoutKeyWidth; +}; + +export interface TouchLayoutFlick { + n?: TouchLayoutSubKey; + s?: TouchLayoutSubKey; + e?: TouchLayoutSubKey; + w?: TouchLayoutSubKey; + ne?: TouchLayoutSubKey; + nw?: TouchLayoutSubKey; + se?: TouchLayoutSubKey; + sw?: TouchLayoutSubKey; +}; diff --git a/common/web/types/src/kmx/element-string.ts b/common/web/types/src/kmx/element-string.ts new file mode 100644 index 00000000000..581ab683cd1 --- /dev/null +++ b/common/web/types/src/kmx/element-string.ts @@ -0,0 +1,79 @@ +import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { Strs, StrsItem } from './kmx-plus.js'; + +export enum ElemElementFlags { + none = 0, + unicode_set = constants.elem_flags_unicode_set, + tertiary_base = constants.elem_flags_tertiary_base, + prebase = constants.elem_flags_prebase +} +; + +export class ElemElement { + value: StrsItem; // UnicodeSet or UCS32LE character + order: number; // -128 to +127; used only by reorder element values + tertiary: number; // -128 to +127; used only by reorder element values + flags: ElemElementFlags; + isEqual(a: ElemElement) { + return a.value === this.value && + a.order === this.order && + a.tertiary === this.tertiary && + a.flags === this.flags; + } +} +; + +export class ElementString extends Array { + constructor(strs: Strs, source: string, order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) { + super(); + //TODO-LDML: full UnicodeSet and parsing + if(!source) { + return; + } + + let items = source.split(""); + + let orders: Array = order ? order.split(" ") : []; + if(orders.length == 1) { + orders = Array(items.length).fill(orders[0]); + } + + let tertiaries: Array = tertiary ? tertiary.split(" ") : []; + if(tertiaries.length == 1) { + tertiaries = Array(items.length).fill(tertiaries[0]); + } + + let tertiary_bases: Array = tertiary_base ? tertiary_base.split(" ") : []; + if(tertiary_bases.length == 1) { + tertiary_bases = Array(items.length).fill(tertiary_bases[0]); + } + + let prebases: Array = prebase ? prebase.split(" ") : []; + if(prebases.length == 1) { + prebases = Array(items.length).fill(prebases[0]); + } + + for(let i = 0; i < items.length; i++) { + let elem = new ElemElement(); + elem.value = strs.allocString(items[i]); + elem.order = orders.length ? parseInt(orders[i], 10) : 0; + elem.tertiary = tertiaries.length ? parseInt(tertiaries[i], 10) : 0; + elem.flags = ElemElementFlags.none | + (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); + }; + } + isEqual(a: ElementString): boolean { + if (a.length != this.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (!this[i].isEqual(a[i])) { + return false; + } + } + return true; + } +} +; diff --git a/common/web/types/src/kmx/kmx-builder.ts b/common/web/types/src/kmx/kmx-builder.ts new file mode 100644 index 00000000000..5b41c1d4387 --- /dev/null +++ b/common/web/types/src/kmx/kmx-builder.ts @@ -0,0 +1,287 @@ +import * as r from 'restructure'; +import { KMXFile, GROUP, KEY, STORE } from './kmx.js'; +import { KMXPlusFile } from './kmx-plus.js'; +import KMXPlusBuilder from './kmx-plus-builder/kmx-plus-builder.js'; + +// These type-checking structures are here to ensure that +// we match the structures from kmx.ts in the generator +// +// They are used internally when building a .kmx file + +interface BUILDER_COMP_KEYBOARD { + dwIdentifier: number; + dwFileVersion: number; + dwCheckSum: number; + KeyboardID: number; + IsRegistered: number; + version: number; + + cxStoreArray: number; + cxGroupArray: number; + + dpStoreArray: number; + dpGroupArray: number; + + StartGroup_ANSI: number; + StartGroup_Unicode: number; + + dwFlags: number; + + dwHotKey: number; + + dpBitmapOffset: number; + dwBitmapSize: number; +}; + +interface BUILDER_COMP_KEYBOARD_KMXPLUSINFO { + dpKMXPlus: number; + dwKMXPlusSize: number; +}; + +interface BUILDER_COMP_STORE { + dwSystemID: number; + dpName: number; + dpString: number; +}; + +interface BUILDER_COMP_KEY { + Key: number; + _padding: number; + Line: number; + ShiftFlags: number; + dpOutput: number; + dpContext: number; +}; + +interface BUILDER_COMP_GROUP { + dpName: number; + dpKeyArray: number; + dpMatch: number; + dpNoMatch: number; + cxKeyArray: number; + fUsingKeys: number; +}; + + +export default class KMXBuilder { + file: KMXFile; + base_keyboard: number = 0; + base_kmxplus: number = 0; + comp_header: BUILDER_COMP_KEYBOARD; + comp_kmxplus: BUILDER_COMP_KEYBOARD_KMXPLUSINFO; + comp_stores: {base: number, store: STORE, obj: BUILDER_COMP_STORE}[] = []; + comp_groups: {base: number, group: GROUP, obj: BUILDER_COMP_GROUP, keys: {base: number, key: KEY, obj: BUILDER_COMP_KEY}[]}[] = []; + comp_kmxplus_data: Uint8Array; + writeDebug: boolean = false; + + constructor(file: KMXFile, writeDebug: boolean) { + this.file = file; + this.writeDebug = writeDebug; + } + + calculateStringOffsetAndSize(string: string, base: number, requireString: boolean = false) { + if(string.length == 0 && !requireString) { + // Zero length strings take up no space in the file, and + // are treated as a 'null string' + return [0, base]; + } + return [base, base + string.length * 2 + 2]; // include trailing zero + } + + private build() { + this.base_keyboard = 0; + this.base_kmxplus = 0; + + // Header + + this.comp_header = { + dwIdentifier: KMXFile.FILEID_COMPILED, + dwFileVersion: KMXFile.VERSION_160, + dwCheckSum: 0, // Deprecated in Keyman 16.0 + KeyboardID: 0, + IsRegistered: 1, + version: 0, + cxStoreArray: 0, + cxGroupArray: 0, + dpStoreArray: 0, + dpGroupArray: 0, + StartGroup_ANSI: 0xFFFFFFFF, + StartGroup_Unicode: 0xFFFFFFFF, + dwFlags: 0, + dwHotKey: 0, + dpBitmapOffset: 0, + dwBitmapSize: 0 + }; + + let size = KMXFile.COMP_KEYBOARD_SIZE; + + if(this.file instanceof KMXPlusFile) { + // Reserve space for KMXPlus header; we'll come back and fill in details + // once we know base kmx file size. + this.comp_header.dwFlags |= KMXFile.KF_KMXPLUS; + this.base_kmxplus = size; + size += KMXFile.COMP_KEYBOARD_KMXPLUSINFO_SIZE; + } + + // Stores + + this.comp_header.cxStoreArray = this.file.keyboard.stores.length; + this.comp_header.dpStoreArray = this.comp_header.cxStoreArray ? size : 0; + let storeBase = size; + size += this.file.keyboard.stores.length * KMXFile.COMP_STORE_SIZE; + for(let store of this.file.keyboard.stores) { + let comp_store: BUILDER_COMP_STORE = { + dwSystemID: store.dwSystemID, + dpName: 0, + dpString: 0 + }; + this.comp_stores.push({base: storeBase, store: store, obj: comp_store}); + if(this.writeDebug /*TODO: || store.isOption*/) { + [comp_store.dpName, size] = this.calculateStringOffsetAndSize(store.dpName, size); + } + + [comp_store.dpString, size] = this.calculateStringOffsetAndSize(store.dpString, size); + storeBase += KMXFile.COMP_STORE_SIZE; + } + + // Groups + + this.comp_header.cxGroupArray = this.file.keyboard.groups.length; + this.comp_header.dpGroupArray = this.comp_header.cxGroupArray ? size : 0; + let groupBase = size; + size += this.file.keyboard.groups.length * KMXFile.COMP_GROUP_SIZE; + for(let group of this.file.keyboard.groups) { + let comp_group: BUILDER_COMP_GROUP = { + dpName: 0, + dpKeyArray: 0, + dpMatch: 0, + dpNoMatch: 0, + cxKeyArray: group.keys.length, + fUsingKeys: group.fUsingKeys ? 1 : 0 + }; + + let comp_keys: {base: number, key: KEY, obj: BUILDER_COMP_KEY}[] = []; + + this.comp_groups.push({base: groupBase, group: group, obj: comp_group, keys: comp_keys}); + + if(this.writeDebug) { + [comp_group.dpName, size] = this.calculateStringOffsetAndSize(group.dpName, size); + } + [comp_group.dpMatch, size] = this.calculateStringOffsetAndSize(group.dpMatch, size); + [comp_group.dpNoMatch, size] = this.calculateStringOffsetAndSize(group.dpNoMatch, size); + + // Keys within a group + + comp_group.dpKeyArray = group.keys.length ? size : 0; + + let keyBase = size; + size += group.keys.length * KMXFile.COMP_KEY_SIZE; + for(let key of group.keys) { + let comp_key: BUILDER_COMP_KEY = { + Key: key.Key, + _padding: 0, + Line: key.Line, + ShiftFlags: key.ShiftFlags, + dpOutput: 0, + dpContext: 0 + }; + comp_keys.push({base: keyBase, key: key, obj: comp_key}); + [comp_key.dpOutput, size] = this.calculateStringOffsetAndSize(key.dpOutput, size, true); + [comp_key.dpContext, size] = this.calculateStringOffsetAndSize(key.dpContext, size, true); + keyBase += KMXFile.COMP_KEY_SIZE; + } + + groupBase += KMXFile.COMP_GROUP_SIZE; + } + + size += this.buildBitmap(); + size += this.buildKMXPlus(size); + + return size; + } + + buildBitmap() { + // TODO + return 0; + } + + buildKMXPlus(base: number) { + if(!(this.file instanceof KMXPlusFile)) { + return 0; + } + + const plusbuilder: KMXPlusBuilder = new KMXPlusBuilder(this.file, this.writeDebug); + this.comp_kmxplus_data = plusbuilder.compile(); + this.comp_kmxplus = { + dpKMXPlus: base, + dwKMXPlusSize: this.comp_kmxplus_data.length + }; + + return this.comp_kmxplus.dwKMXPlusSize; + } + + setString(file: Uint8Array, pos: number, str: string, requireString: boolean = false): void { + if(requireString && !str.length) { + // Just write zero terminator, as r.String for a zero-length string + // seems to fail. + let sbuf = r.uint16le; + file.set(sbuf.toBuffer(0), pos); + } + else if(pos && str.length) { + let sbuf = new r.String(null, 'utf16le'); // null-terminated string + file.set(sbuf.toBuffer(str), pos); + } + } + + compile(): Uint8Array { + const fileSize = this.build(); + + let file: Uint8Array = new Uint8Array(fileSize); + + // Write headers + + const header = this.file.COMP_KEYBOARD.toBuffer(this.comp_header); + file.set(header, this.base_keyboard); + + if(this.file instanceof KMXPlusFile) { + const kmxplus: Uint8Array = this.file.COMP_KEYBOARD_KMXPLUSINFO.toBuffer(this.comp_kmxplus); + file.set(kmxplus, this.base_kmxplus); + } + + // Write store array and data + + for(let store of this.comp_stores) { + file.set(this.file.COMP_STORE.toBuffer(store.obj), store.base); + if(this.writeDebug) { + this.setString(file, store.obj.dpName, store.store.dpName); + } + this.setString(file, store.obj.dpString, store.store.dpString); + } + + // Write group array and data + + for(let group of this.comp_groups) { + file.set(this.file.COMP_GROUP.toBuffer(group.obj), group.base); + if(this.writeDebug) { + this.setString(file, group.obj.dpName, group.group.dpName); + } + this.setString(file, group.obj.dpMatch, group.group.dpMatch); + this.setString(file, group.obj.dpNoMatch, group.group.dpNoMatch); + + for(let key of group.keys) { + file.set(this.file.COMP_KEY.toBuffer(key.obj), key.base); + // for back-compat reasons, these are never NULL strings + this.setString(file, key.obj.dpContext, key.key.dpContext, true); + this.setString(file, key.obj.dpOutput, key.key.dpOutput, true); + } + } + + // Write KMXPlus data stream + + if(this.file instanceof KMXPlusFile) { + file.set(this.comp_kmxplus_data, this.comp_kmxplus.dpKMXPlus); + } + + return file; + } +} \ No newline at end of file diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-disp.ts b/common/web/types/src/kmx/kmx-plus-builder/build-disp.ts new file mode 100644 index 00000000000..512bcca0b2f --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-disp.ts @@ -0,0 +1,46 @@ +import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { KMXPlusData } from "../kmx-plus.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_SECTION } from './builder-section.js'; + +/* ------------------------------------------------------------------ + * disp section + ------------------------------------------------------------------ */ + +/** + * Builder for the 'disp' section + */ +interface BUILDER_DISP_ITEM { + to: number; + display: number; +}; + +export interface BUILDER_DISP extends BUILDER_SECTION { + count: number; + baseCharacter: number; + items: BUILDER_DISP_ITEM[]; +}; + +export function build_disp(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_DISP { + if(!kmxplus.disp.disps.length && !kmxplus.disp.baseCharacter.value) { + return null; + } + + let disp: BUILDER_DISP = { + ident: constants.hex_section_id(constants.section.disp), + size: constants.length_disp + constants.length_disp_item * kmxplus.disp.disps.length, + _offset: 0, + count: kmxplus.disp.disps.length, + baseCharacter: build_strs_index(sect_strs, kmxplus.disp.baseCharacter), + items: [] + }; + + for(let item of kmxplus.disp.disps) { + disp.items.push({ + to: build_strs_index(sect_strs, item.to), + display: build_strs_index(sect_strs, item.display), + }); + } + + return disp; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-elem.ts b/common/web/types/src/kmx/kmx-plus-builder/build-elem.ts new file mode 100644 index 00000000000..0c579037d72 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-elem.ts @@ -0,0 +1,103 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { ElementString } from "../element-string.js"; +import { Elem } from "../kmx-plus.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/* ------------------------------------------------------------------ + * elem section + ------------------------------------------------------------------ */ + +interface BUILDER_ELEM_ELEMENT { + element: number; // str | UTF-32 char + flags: number; + _value: string; +}; + +interface BUILDER_ELEM_STRING { + offset: number; + length: number; + items: BUILDER_ELEM_ELEMENT[]; + _value: ElementString; +}; + +/** + * Builder for the 'elem' section + */ +export interface BUILDER_ELEM extends BUILDER_SECTION { + count: number; + strings: BUILDER_ELEM_STRING[]; +}; + +function binaryElemCompare(a: BUILDER_ELEM_STRING, b: BUILDER_ELEM_STRING): number { + for(let i = 0; i < a.items.length && i < b.items.length; i++) { + if(a.items[i]._value < b.items[i]._value) return -1; + if(a.items[i]._value > b.items[i]._value) return 1; + if(a.items[i].flags < b.items[i].flags) return -1; + if(a.items[i].flags > b.items[i].flags) return 1; + } + if(a.items.length < b.items.length) return -1; + if(a.items.length > b.items.length) return 1; + return 0; +} + +export function build_elem(source_elem: Elem, sect_strs: BUILDER_STRS): BUILDER_ELEM { + let result: BUILDER_ELEM = { + ident: constants.hex_section_id(constants.section.elem), + size: 0, // finalized below + _offset: 0, + count: source_elem.strings.length, + strings: [], // finalized below + }; + + // TODO: consider padding + result.strings = source_elem.strings.map(item => { + let res: BUILDER_ELEM_STRING = { + offset: 0, // finalized below + length: item.length, + items: [], + _value: item + }; + + res.items = item.map(v => { + return { + element: build_strs_index(sect_strs, v.value), + flags: constants.elem_flags_unicode_set | + v.flags | // + ((v.order ?? 0) << constants.elem_flags_order_bitshift) | // -128 to +127; used only by reorder element values + ((v.tertiary ?? 0) << constants.elem_flags_tertiary_bitshift), // -128 to +127; used only by reorder element values + _value: v.value.value + }; + }); + return res; + }); + result.strings.sort((a,b) => binaryElemCompare(a, b)); + + /* Calculate offsets and total size */ + + let offset = constants.length_elem + constants.length_elem_item * result.count; + for(let item of result.strings) { + if (item.length === 0) { + // no length gets a zero offset + item.offset = 0; + } else { + item.offset = offset; + offset += item.length * constants.length_elem_item_element; + } + } + + result.size = offset; + return result; +} + +export function build_elem_index(sect_elem: BUILDER_ELEM, value: ElementString) { + if(!(value instanceof ElementString)) { + throw new Error('unexpected value '+value); + } + + const result = sect_elem.strings.findIndex(v => value.isEqual(v._value)); + if(result < 0) { + throw new Error('unexpectedly missing StrsItem '+value); + } + return result; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-keys.ts b/common/web/types/src/kmx/kmx-plus-builder/build-keys.ts new file mode 100644 index 00000000000..0e9d38e9086 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-keys.ts @@ -0,0 +1,175 @@ + +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KeysFlick, KMXPlusData, StrsItem } from "../kmx-plus.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { build_list_index, BUILDER_LIST } from "./build-list.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/* ------------------------------------------------------------------ + * keys section + ------------------------------------------------------------------ */ + +/** + * This struct is a single in the keys keybag + */ +interface BUILDER_KEYS_KEY { + to: number; // str or single codepoint + flags: number; + id: number; // str with original key id + _id: string; // original key id, for sorting + switch: number; // str with layer of new l + width: number; // ceil((width||1)*10), so 12 for width 1.2 + longPress: number; // list of longPress sequences + longPressDefault: number; // str with the default longPress target + multiTap: number; // list of multiTap sequences + flicks: number; // index into the flicks[] subtable for this flick list +}; + +/** + * This is a , a list of elements. + */ +interface BUILDER_KEYS_FLICKS { + count: number; // number of BUILDER_KEYS_FLICK entries in this flick list + flick: number; // index into the flick[] subtable of the first flick in the list + id: number; // str with the original id of this flicks + _id: string; // copy of the flicks id, used for sorting during build + _flicks: KeysFlick[]; // temporary copy of KeysFlick object +}; + +/** + * This is a single element. + */ +interface BUILDER_KEYS_FLICK { + directions: number; // list of cardinal/intercardinal directions + flags: number; // + to: number; // str or single codepoint +}; + + +interface BUILDER_KEYS_KMAP { + vkey: number; + mod: number; + key: number; //index to keys.key +}; + +/** + * Builder for the 'keys' section + */ +export interface BUILDER_KEYS extends BUILDER_SECTION { + ident: number; + size: number; + keyCount: number; + flicksCount: number; + flickCount: number; + kmapCount: number; + keys: BUILDER_KEYS_KEY[]; + flicks: BUILDER_KEYS_FLICKS[]; + flick: BUILDER_KEYS_FLICK[]; + kmap: BUILDER_KEYS_KMAP[]; +}; + +export function build_keys(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_list: BUILDER_LIST): BUILDER_KEYS { + if(kmxplus.keys.keys.length == 0 && + (kmxplus.keys.flicks.length <= 1)) { // if no keys and only the 'null' flick. + return null; + } + + let keys: BUILDER_KEYS = { + ident: constants.hex_section_id(constants.section.keys), + size: 0, + keyCount: kmxplus.keys.keys.length, + flicksCount: kmxplus.keys.flicks.length, + flickCount: 0, + kmapCount: kmxplus.keys.kmap.length, + keys: [], + flicks: [], + flick: [], + kmap: [], + _offset: 0, + }; + + // flicks first: the keys will need to index into the flicks table. + + // Note that per the Keys class and spec, there is always a flicks=0 meaning 'no flicks' + keys.flicks = kmxplus.keys.flicks.map((flicks) => { + let result : BUILDER_KEYS_FLICKS = { + count: flicks.flicks.length, + flick: keys.flick.length, // index of first flick + id: build_strs_index(sect_strs, flicks.id), + _id: flicks.id.value, + _flicks: flicks.flicks, + }; + return result; + }); + // Sort the flicks array by id + keys.flicks.sort((a, b) => StrsItem.binaryStringCompare(a._id, b._id)); + // now, allocate 'flick' entries for each 'flicks' + keys.flicks.forEach((flicks) => { + flicks._flicks.forEach((flick) => { + keys.flick.push({ + directions: build_list_index(sect_list, flick.directions), + flags: flick.flags, + to: build_strs_index(sect_strs, flick.to), + }); + keys.flickCount++; + }); + }); + + // now, keys + keys.keys = kmxplus.keys.keys.map((key) => { + let result : BUILDER_KEYS_KEY = { + to: build_strs_index(sect_strs, key.to), + flags: key.flags, + id: build_strs_index(sect_strs, key.id), + _id: key.id.value, + switch: build_strs_index(sect_strs, key.switch), + width: key.width, + longPress: build_list_index(sect_list, key.longPress), + longPressDefault: build_strs_index(sect_strs, key.longPressDefault), + multiTap: build_list_index(sect_list, key.multiTap), + flicks: keys.flicks.findIndex(v => v._id === (key.flicks || '')), // flicks id='' is the 'null' flicks + }; + // Make sure the flicks were found + if (result.flicks === -1) { + throw new Error(`Keys: Could not find flicks id=${key.flicks} for key=${key.id.value}`); + } + return result; + }); + // sort the keys by id + keys.keys.sort((a, b) => StrsItem.binaryStringCompare(a._id, b._id)); + + // finally, kmap + keys.kmap = kmxplus.keys.kmap.map(({vkey, mod, key}) => { + let result : BUILDER_KEYS_KMAP = { + vkey, + mod, + key: keys.keys.findIndex(k => k._id === key), + }; + // Make sure the key was found + if (result.key === -1) { + throw new Error(`Keys: Could not find keys.key id=${result.key} for keys.kmap.key=${key}`); + } + return result; + }); + + // Sort kmap by vkey, mod order, per C7043 + keys.kmap.sort((a,b) => { + let rc = 0; + if (rc === 0) { + rc = (a.vkey - b.vkey); + } + if (rc === 0) { + rc = (a.mod - b.mod); + } + return rc; + }); + + let offset = constants.length_keys + + (constants.length_keys_key * keys.keyCount) + + (constants.length_keys_flick_element * keys.flickCount) + + (constants.length_keys_flick_list * keys.flicksCount) + + (constants.length_keys_kmap * keys.kmapCount); + keys.size = offset; + + return keys; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-layr.ts b/common/web/types/src/kmx/kmx-plus-builder/build-layr.ts new file mode 100644 index 00000000000..da4e79f38f5 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-layr.ts @@ -0,0 +1,154 @@ + +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlusData, LayrEntry, LayrRow, StrsItem } from "../kmx-plus.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_LIST } from "./build-list.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/* ------------------------------------------------------------------ + * layr section - + ------------------------------------------------------------------ */ + +/** + * List of layers, the element + */ +interface BUILDER_LAYR_LIST { + hardware: number; // hardware indicator + layer: number; // index of first layer in the list, in the + count: number; // number of layer entries in the list + minDeviceWidth: number; // width in millimeters + _layers: LayrEntry[]; // original layer entry, for in-memory only +}; + +/** + * element + */ +interface BUILDER_LAYR_LAYER { + id: number; // str of layer id + _id: string; // original layer id, for sorting + mod: number; // bitfield with modifier info + row: number; // row index into row subtable + _rows: LayrRow[]; // original rows, for in-memory only + count: number; // number of row entries +}; + +/** + * element + */ +interface BUILDER_LAYR_ROW { + key: number; // index into key subtable + count: number; // number of keys +}; + +/** + * portion of keys attribute of + */ +interface BUILDER_LAYR_KEY { + key: number; +}; + +/** + * Builder for the 'keys' section + */ +export interface BUILDER_LAYR extends BUILDER_SECTION { + listCount: number, // number of entries in lists subtable + layerCount: number, // number of entries in layers subtable + rowCount: number, // number of entries in rows subtable + keyCount: number, // number of entries in keys subtable + lists: BUILDER_LAYR_LIST[], // subtable of elements + layers: BUILDER_LAYR_LAYER[], // subtable of elements + rows: BUILDER_LAYR_ROW[], // subtable of elements + keys: BUILDER_LAYR_KEY[], // subtable of key entries +}; + +export function build_layr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_list: BUILDER_LIST): BUILDER_LAYR { + if (!kmxplus.layr?.lists) { + return null; // if there aren't any layers at all (which should be an invalid keyboard) + } + + let layr: BUILDER_LAYR = { + ident: constants.hex_section_id(constants.section.layr), + size: constants.length_layr, + _offset: 0, + listCount: kmxplus.layr.lists.length, + layerCount: 0, // calculated below + rowCount: 0, // calculated below + keyCount: 0, // calculated below + lists: [], + layers: [], + rows: [], + keys: [] + }; + + layr.lists = kmxplus.layr.lists.map((list) => { + const blist: BUILDER_LAYR_LIST = { + hardware: list.hardware, + layer: null, // to be set below + _layers: list.layers, + count: list.layers.length, + minDeviceWidth: list.minDeviceWidth, + }; + return blist; + }); + // now sort the lists + layr.lists.sort((a, b) => { + if (a.hardware < b.hardware) { + return -1; + } else if (a.hardware > b.hardware) { + return 1; + } + if (a.minDeviceWidth < b.minDeviceWidth) { + return -1; + } else if (a.minDeviceWidth > b.minDeviceWidth) { + return 1; + } else { + return 0; // same + } + }); + // Now allocate the layers, rows, and keys + layr.lists.forEach((list) => { + list.layer = layr.layers.length; // index to first layer in list + const blayers = list._layers.map((layer) => { + const blayer: BUILDER_LAYR_LAYER = { + _id: layer.id.value, // original id + id: build_strs_index(sect_strs, layer.id), + mod: layer.mod, + row: null, // row ID, to be filled in + _rows: layer.rows, // temporary + count: layer.rows.length, // number of rows + }; + return blayer; + }); + // sort the new layers + blayers.sort((a, b) => StrsItem.binaryStringCompare(a._id, b._id)); + blayers.forEach((layer) => { + layer.row = layr.rows.length; // index to first row in list + layer._rows.forEach((row) => { + const brow: BUILDER_LAYR_ROW = { + key: layr.keys.length, + count: row.keys.length, + }; + row.keys.forEach((key) => { + const bkey: BUILDER_LAYR_KEY = { + key: build_strs_index(sect_strs, key), + }; + layr.keys.push(bkey); + }); + layr.rows.push(brow); + }); + layr.layers.push(layer); + }); + }); + + layr.layerCount = layr.layers.length; + layr.rowCount = layr.rows.length; + layr.keyCount = layr.keys.length; + + let offset = constants.length_layr + + (constants.length_layr_list * layr.listCount) + + (constants.length_layr_entry * layr.layerCount) + + (constants.length_layr_row * layr.rowCount) + + (constants.length_layr_key * layr.keyCount); + layr.size = offset; + return layr; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-list.ts b/common/web/types/src/kmx/kmx-plus-builder/build-list.ts new file mode 100644 index 00000000000..a1ed9504632 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-list.ts @@ -0,0 +1,95 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { List, ListItem } from "../kmx-plus.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/* ------------------------------------------------------------------ + * list section + ------------------------------------------------------------------ */ + +/** + * A list entry. + */ +interface BUILDER_LIST_LIST { + index: number; // index into indices[] subtable + count: number; // number of strings in this list + _value: ListItem; // for locating the list during finalization +}; + +interface BUILDER_LIST_INDEX { + str: number; // str for this string + _value: string; // for locating this string during finalization +}; + +/** + * Builder for the 'list' section + */ +export interface BUILDER_LIST extends BUILDER_SECTION { + listCount: number; // Number of lists total in the subtable + indexCount: number; // Total number of indices in the subtable + lists: BUILDER_LIST_LIST[]; + indices: BUILDER_LIST_INDEX[]; +}; + +export function build_list(source_list: List, sect_strs: BUILDER_STRS): BUILDER_LIST { + if(!source_list?.lists?.length) { + // there's always the null list + return null; + } + + let result: BUILDER_LIST = { + ident: constants.hex_section_id(constants.section.list), + size: 0, + _offset: 0, + listCount: source_list.lists.length, + indexCount: 0, + lists: [], + indices: [], + }; + + result.lists = source_list.lists.map(array => { + let list : BUILDER_LIST_LIST = { + index: result.indices.length, // the next indexcount + count: array.length, + _value: array + }; + array.forEach((i) => { + let index : BUILDER_LIST_INDEX = { + // Get the final string index + str: build_strs_index(sect_strs, i.value), + _value: i.value.value, // unwrap the actual string value + }; + result.indices.push(index); // increment the indexCount + result.indexCount++; + }); + return list; + }); + + // Sort the lists. + result.lists.sort((a,b) => a._value.compareTo(b._value)); + + let offset = constants.length_list + + (constants.length_list_item * result.listCount) + + (constants.length_list_index * result.indexCount); + result.size = offset; + + return result; +} + +/** + * Returns the index into the list, analagous to build_strs_index + * @param sect_strs + * @param value + * @returns + */ +export function build_list_index(sect_list: BUILDER_LIST, value: ListItem) { + if(!(value instanceof ListItem)) { + throw new Error('unexpected value '+ value); + } + + let result = sect_list.lists.findIndex(v => v._value === value); + if(result < 0) { + throw new Error('unexpectedly missing ListItem ' + value); // TODO-LDML: it's an array of strs + } + return result; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-loca.ts b/common/web/types/src/kmx/kmx-plus-builder/build-loca.ts new file mode 100644 index 00000000000..e7d3198fc27 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-loca.ts @@ -0,0 +1,34 @@ + +/* ------------------------------------------------------------------ + * loca section + ------------------------------------------------------------------ */ + +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlusData } from "../kmx-plus.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/** + * Builder for the 'loca' section + */ +export interface BUILDER_LOCA extends BUILDER_SECTION { + count: number; + items: number[]; //str[] +}; + +export function build_loca(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_LOCA { + let loca: BUILDER_LOCA = { + ident: constants.hex_section_id(constants.section.loca), + size: constants.length_loca + constants.length_loca_item * kmxplus.loca.locales.length, + _offset: 0, + count: kmxplus.loca.locales.length, + items: [] + }; + + for(let item of kmxplus.loca.locales) { + loca.items.push(build_strs_index(sect_strs, item)); + } + + return loca; +} + diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-meta.ts b/common/web/types/src/kmx/kmx-plus-builder/build-meta.ts new file mode 100644 index 00000000000..5c60472f356 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-meta.ts @@ -0,0 +1,37 @@ + +/* ------------------------------------------------------------------ + * meta section + ------------------------------------------------------------------ */ + +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlusData } from "../kmx-plus.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/** + * Builder for the 'meta' section + */ +export interface BUILDER_META extends BUILDER_SECTION { + author: number; //str + conform: number; //str + layout: number; //str + normalization: number; //str + indicator: number; //str + version: number; //str + settings: number; //bitfield +}; + +export function build_meta(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_META { + return { + ident: constants.hex_section_id(constants.section.meta), + size: constants.length_meta, + _offset: 0, + author: build_strs_index(sect_strs, kmxplus.meta.author), + conform: build_strs_index(sect_strs, kmxplus.meta.conform), + layout: build_strs_index(sect_strs, kmxplus.meta.layout), + normalization: build_strs_index(sect_strs, kmxplus.meta.normalization), + indicator: build_strs_index(sect_strs, kmxplus.meta.indicator), + version: build_strs_index(sect_strs, kmxplus.meta.version), + settings: kmxplus.meta.settings ?? 0, + }; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-name.ts b/common/web/types/src/kmx/kmx-plus-builder/build-name.ts new file mode 100644 index 00000000000..9397ff0b0a6 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-name.ts @@ -0,0 +1,37 @@ + +/* ------------------------------------------------------------------ + * name section + ------------------------------------------------------------------ */ + +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlusData } from "../kmx-plus.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/** + * Builder for the 'name' section + */ +export interface BUILDER_NAME extends BUILDER_SECTION { + count: number; + items: number[]; //str[] +}; + +export function build_name(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS): BUILDER_NAME { + if(!kmxplus.name.names.length) { + return null; + } + + let name: BUILDER_NAME = { + ident: constants.hex_section_id(constants.section.name), + size: constants.length_name + constants.length_name_item * kmxplus.name.names.length, + _offset: 0, + count: kmxplus.name.names.length, + items: [] + }; + + for(let item of kmxplus.name.names) { + name.items.push(build_strs_index(sect_strs, item)); + } + + return name; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-ordr.ts b/common/web/types/src/kmx/kmx-plus-builder/build-ordr.ts new file mode 100644 index 00000000000..948bf769934 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-ordr.ts @@ -0,0 +1,45 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlusData } from "../kmx-plus.js"; +import { build_elem_index, BUILDER_ELEM } from "./build-elem.js"; +import { BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/* ------------------------------------------------------------------ +* ordr section + ------------------------------------------------------------------ */ + +interface BUILDER_ORDR_ITEM { + elements: number; //elem.string + before: number; //elem.string +}; + +export interface BUILDER_ORDR extends BUILDER_SECTION { + count: number; + items: BUILDER_ORDR_ITEM[]; +}; + +/** +* Builder for the 'ordr' section +*/ +export function build_ordr(kmxplus: KMXPlusData, sect_strs: BUILDER_STRS, sect_elem: BUILDER_ELEM): BUILDER_ORDR { + if(!kmxplus.ordr?.items.length) { + return null; + } + + let ordr: BUILDER_ORDR = { + ident: constants.hex_section_id(constants.section.ordr), + size: constants.length_ordr + constants.length_ordr_item * kmxplus.ordr.items.length, + _offset: 0, + count: kmxplus.ordr.items.length, + items: [] + }; + + for(let item of kmxplus.ordr.items) { + ordr.items.push({ + elements: build_elem_index(sect_elem, item.elements), + before: build_elem_index(sect_elem, item.before) + }); + } + + return ordr; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-sect.ts b/common/web/types/src/kmx/kmx-plus-builder/build-sect.ts new file mode 100644 index 00000000000..b97555d6de0 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-sect.ts @@ -0,0 +1,31 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/* ------------------------------------------------------------------ + * sect section + ------------------------------------------------------------------ */ + +interface BUILDER_SECT_ITEM { + sect: number; + offset: number; //? new r.VoidPointer(r.uint32le, {type: 'global'}) +}; + +/** + * Builder for the 'sect' (Section table of contents) section + */ +export interface BUILDER_SECT extends BUILDER_SECTION { + total: number; + count: number; + items: BUILDER_SECT_ITEM[]; + }; + +export function build_sect(): BUILDER_SECT { + return { + ident: constants.hex_section_id(constants.section.sect), + size: 0, // finalized later + _offset: 0, + total: 0, // finalized later + count: 0, // finalized later + items: [], // finalized later + }; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-strs.ts b/common/web/types/src/kmx/kmx-plus-builder/build-strs.ts new file mode 100644 index 00000000000..a8b9c91c75c --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-strs.ts @@ -0,0 +1,58 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { Strs, StrsItem } from "../kmx-plus.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/* ------------------------------------------------------------------ + * strs section + ------------------------------------------------------------------ */ + +interface BUILDER_STRS_ITEM { + // While we use length which is number of utf-16 code units excluding null terminator, + // we always write a null terminator, so we can get restructure to do that for us here + offset: number; //? new r.Pointer(r.uint32le, new r.String(null, 'utf16le')), + length: number; // in UTF-16 code units + _value: string +}; + +/** + * Builder for the 'strs' section + */ +export interface BUILDER_STRS extends BUILDER_SECTION { + count: number; + items: BUILDER_STRS_ITEM[]; +}; + +export function build_strs(source_strs: Strs): BUILDER_STRS { + let result: BUILDER_STRS = { + ident: constants.hex_section_id(constants.section.strs), + size: 0, // finalized later + _offset: 0, + count: source_strs.strings.length, + items: [], // filled below + }; + + result.items = source_strs.strings.map(item => { return {_value: item.value, length: item.value.length, offset: 0}; }); + result.items.sort((a,b) => StrsItem.binaryStringCompare(a._value, b._value)); + + let offset = constants.length_strs + constants.length_strs_item * result.count; + // TODO: consider padding + for(let item of result.items) { + item.offset = offset; + offset += item.length * 2 + 2; /* UTF-16 code units + sizeof null terminator */ + } + result.size = offset; + + return result; +} + +export function build_strs_index(sect_strs: BUILDER_STRS, value: StrsItem) { + if(!(value instanceof StrsItem)) { + throw new Error('unexpected value '+ value); + } + + let result = sect_strs.items.findIndex(v => v._value === value.value); + if(result < 0) { + throw new Error('unexpectedly missing StrsItem '+value.value); + } + return result; +} diff --git a/common/web/types/src/kmx/kmx-plus-builder/build-tran.ts b/common/web/types/src/kmx/kmx-plus-builder/build-tran.ts new file mode 100644 index 00000000000..c700d1ea085 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-tran.ts @@ -0,0 +1,50 @@ + +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { Bksp, Finl, Tran } from "../kmx-plus.js"; +import { build_elem_index, BUILDER_ELEM } from "./build-elem.js"; +import { build_strs_index, BUILDER_STRS } from "./build-strs.js"; +import { BUILDER_SECTION } from "./builder-section.js"; + +/* ------------------------------------------------------------------ +* tran section + ------------------------------------------------------------------ */ + +interface BUILDER_TRAN_ITEM { + from: number; //elem.string + to: number; //str + before: number; //elem.string + flags: number; +}; + +export interface BUILDER_TRAN extends BUILDER_SECTION { + count: number; + items: BUILDER_TRAN_ITEM[]; +}; + +/** +* Builder for the 'tran', 'finl', and 'bksp' sections, all of which use the BUILDER_TRAN layout +*/ +export function build_tran(source_tran: Tran|Finl|Bksp, sect_strs: BUILDER_STRS, sect_elem: BUILDER_ELEM): BUILDER_TRAN { + if(!source_tran?.items.length) { + return null; + } + + let tran: BUILDER_TRAN = { + ident: constants.hex_section_id(source_tran.id), + size: constants.length_tran + constants.length_tran_item * source_tran.items.length, + _offset: 0, + count: source_tran.items.length, + items: [] + }; + + for(let item of source_tran.items) { + tran.items.push({ + from: build_elem_index(sect_elem, item.from), + to: build_strs_index(sect_strs, item.to), + before: build_elem_index(sect_elem, item.before), + flags: item.flags + }); + } + + return tran; +} 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 new file mode 100644 index 00000000000..0a4863beabd --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/build-vkey.ts @@ -0,0 +1,43 @@ +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/builder-section.ts b/common/web/types/src/kmx/kmx-plus-builder/builder-section.ts new file mode 100644 index 00000000000..1a6108af4e6 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/builder-section.ts @@ -0,0 +1,6 @@ +export interface BUILDER_SECTION { + ident: number; + size: number; + _offset: number; // used only for building the output +} +; 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 new file mode 100644 index 00000000000..95a36a60f2f --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts @@ -0,0 +1,190 @@ +import * as r from 'restructure'; +import { KMXPlusFile } from "../kmx-plus.js"; +import { constants, SectionIdent } from '@keymanapp/ldml-keyboard-constants'; +import { BUILDER_SECTION } from './builder-section.js'; +import { BUILDER_SECT, build_sect } from './build-sect.js'; +import { BUILDER_DISP, build_disp } from './build-disp.js'; +import { BUILDER_ELEM, build_elem } from './build-elem.js'; +import { BUILDER_KEYS, build_keys } from './build-keys.js'; +import { BUILDER_LAYR, build_layr } from './build-layr.js'; +import { BUILDER_LIST, build_list } from './build-list.js'; +import { BUILDER_LOCA, build_loca } from './build-loca.js'; +import { BUILDER_META, build_meta } from './build-meta.js'; +import { BUILDER_NAME, build_name } from './build-name.js'; +import { BUILDER_ORDR, build_ordr } from './build-ordr.js'; +import { BUILDER_STRS, build_strs } from './build-strs.js'; +import { BUILDER_TRAN, build_tran } from './build-tran.js'; +import { BUILDER_VKEY, build_vkey } from './build-vkey.js'; + +type BUILDER_BKSP = BUILDER_TRAN; +type BUILDER_FINL = BUILDER_TRAN; + +type SectionBuilders = { + // [id in SectionIdent]: BUILDER_SECTION; + sect?: BUILDER_SECT; + bksp?: BUILDER_BKSP; + disp?: BUILDER_DISP; + elem?: BUILDER_ELEM; + finl?: BUILDER_FINL; + keys?: BUILDER_KEYS; + layr?: BUILDER_LAYR; + list?: BUILDER_LIST; + loca?: BUILDER_LOCA; + meta?: BUILDER_META; + name?: BUILDER_NAME; + ordr?: BUILDER_ORDR; + strs?: BUILDER_STRS; + tran?: BUILDER_TRAN; + vkey?: BUILDER_VKEY; +}; + +export default class KMXPlusBuilder { + private file: KMXPlusFile; + //private writeDebug: boolean; + + sect : SectionBuilders = { + + }; + + constructor(file: KMXPlusFile, _writeDebug: boolean) { + this.file = file; + //this.writeDebug = _writeDebug; + } + + public compile(): Uint8Array { + const fileSize = this.build(); + let file: Uint8Array = new Uint8Array(fileSize); + + this.emitSection(file, this.file.COMP_PLUS_SECT, this.sect.sect); + // Keep the rest of these in order. + this.emitSection(file, this.file.COMP_PLUS_BKSP, this.sect.bksp); + this.emitSection(file, this.file.COMP_PLUS_DISP, this.sect.disp); + this.emitSection(file, this.file.COMP_PLUS_ELEM, this.sect.elem); + this.emitElements(file); + this.emitSection(file, this.file.COMP_PLUS_FINL, this.sect.finl); + this.emitSection(file, this.file.COMP_PLUS_KEYS, this.sect.keys); + this.emitSection(file, this.file.COMP_PLUS_LAYR, this.sect.layr); + this.emitSection(file, this.file.COMP_PLUS_LIST, this.sect.list); + this.emitSection(file, this.file.COMP_PLUS_LOCA, this.sect.loca); + this.emitSection(file, this.file.COMP_PLUS_META, this.sect.meta); + this.emitSection(file, this.file.COMP_PLUS_NAME, this.sect.name); + this.emitSection(file, this.file.COMP_PLUS_ORDR, this.sect.ordr); + this.emitSection(file, this.file.COMP_PLUS_STRS, this.sect.strs); + this.emitStrings(file); + this.emitSection(file, this.file.COMP_PLUS_TRAN, this.sect.tran); + this.emitSection(file, this.file.COMP_PLUS_VKEY, this.sect.vkey); + + return file; + } + + private build() { + // Required sections: sect, strs, loca, meta + + // We must prepare the strs, list, and elem sections early so that other sections can + // reference them. However, they will be emitted in alpha order. + this.sect.strs = build_strs(this.file.kmxplus.strs); + this.sect.list = build_list(this.file.kmxplus.list, this.sect.strs); + this.sect.elem = build_elem(this.file.kmxplus.elem, this.sect.strs); + + const build_bksp = build_tran; + const build_finl = build_tran; + + this.sect.bksp = build_bksp(this.file.kmxplus.bksp, this.sect.strs, this.sect.elem); + this.sect.disp = build_disp(this.file.kmxplus, this.sect.strs); + this.sect.finl = build_finl(this.file.kmxplus.finl, this.sect.strs, this.sect.elem); + this.sect.keys = build_keys(this.file.kmxplus, this.sect.strs, this.sect.list); + this.sect.layr = build_layr(this.file.kmxplus, this.sect.strs, this.sect.list); + this.sect.loca = build_loca(this.file.kmxplus, this.sect.strs); + this.sect.meta = build_meta(this.file.kmxplus, this.sect.strs); + this.sect.name = build_name(this.file.kmxplus, this.sect.strs); + this.sect.ordr = build_ordr(this.file.kmxplus, this.sect.strs, this.sect.elem); + this.sect.tran = build_tran(this.file.kmxplus.tran, this.sect.strs, this.sect.elem); + this.sect.vkey = build_vkey(this.file.kmxplus); + + // Finalize the sect (index) section + + this.sect.sect = build_sect(); + this.finalize_sect(); // must be done last + return this.sect.sect.total; + } + + private finalize_sect() { + // 'sect' section + + this.sect.sect.count = 0; + + Object.keys(constants.section).forEach((sectstr : string) => { + const sect : SectionIdent = constants.section[sectstr]; + if(this.sect[sect] && sect !== 'sect') { + this.sect.sect.count++; + } + }); + + this.sect.sect.size = constants.length_sect + constants.length_sect_item * this.sect.sect.count; + + let offset = this.sect.sect.size; + // Note: in order! Everyone's here except 'sect' which is at offset 0 + offset = this.finalize_sect_item(this.sect.bksp, offset); + offset = this.finalize_sect_item(this.sect.disp, offset); + offset = this.finalize_sect_item(this.sect.elem, offset); + offset = this.finalize_sect_item(this.sect.finl, offset); + offset = this.finalize_sect_item(this.sect.keys, offset); + offset = this.finalize_sect_item(this.sect.layr, offset); + offset = this.finalize_sect_item(this.sect.list, offset); + offset = this.finalize_sect_item(this.sect.loca, offset); + offset = this.finalize_sect_item(this.sect.meta, offset); + offset = this.finalize_sect_item(this.sect.name, offset); + offset = this.finalize_sect_item(this.sect.ordr, offset); + offset = this.finalize_sect_item(this.sect.strs, offset); + offset = this.finalize_sect_item(this.sect.tran, offset); + offset = this.finalize_sect_item(this.sect.vkey, offset); + + this.sect.sect.total = offset; + } + + private finalize_sect_item(sect: BUILDER_SECTION, offset: number): number { + if(!sect) { + // Don't include null sections + return offset; + } + sect._offset = offset; + this.sect.sect.items.push({sect: sect.ident, offset: offset}); + return offset + sect.size; + } + + private emitSection(file: Uint8Array, comp: any, sect: BUILDER_SECTION) { + if(sect) { + const buf = comp.toBuffer(sect); + if (buf.length > sect.size) { + // buf.length may be < sect.size if there is a variable part (i.e. elem) + throw new RangeError(`Internal Error: Section ${constants.str_section_id(sect.ident)} claimed size ${sect.size} but produced buffer of size ${buf.length}.`); + } + file.set(buf, sect._offset); + } + } + + private emitStrings(file: Uint8Array) { + for(let item of this.sect.strs.items) { + if(item._value === '') { + // We have a special case for the zero-length string + let sbuf = r.uint16le; + file.set(sbuf.toBuffer(0), item.offset + this.sect.strs._offset); + } else { + let sbuf = new r.String(null, 'utf16le'); + file.set(sbuf.toBuffer(item._value), item.offset + this.sect.strs._offset); + } + } + } + + private emitElements(file: Uint8Array) { + if(this.sect.elem) { + for(let str of this.sect.elem.strings) { + if(str.items.length > 0) { + let COMP_PLUS_ELEM_ELEMENTS = new r.Array(this.file.COMP_PLUS_ELEM_ELEMENT, str.items.length); + file.set(COMP_PLUS_ELEM_ELEMENTS.toBuffer(str.items), str.offset + this.sect.elem._offset); + } + } + } + } + +} diff --git a/common/web/types/src/kmx/kmx-plus.ts b/common/web/types/src/kmx/kmx-plus.ts new file mode 100644 index 00000000000..d525d896af9 --- /dev/null +++ b/common/web/types/src/kmx/kmx-plus.ts @@ -0,0 +1,692 @@ +import { constants } from '@keymanapp/ldml-keyboard-constants'; +import * as r from 'restructure'; +import { ElementString } from './element-string.js'; +import { ListItem } from './string-list.js'; +import { unescapeString } from '../util/util.js'; +import { KMXFile } from './kmx.js'; + +// Implementation of file structures from /core/src/ldml/C7043_ldml.md +// Writer in kmx-builder.ts +// Reader in kmx-loader.ts + +export class Section { +} + +export class GlobalSections { + // These sections are used by other sections during compilation + strs: Strs; + elem: Elem; + list: List; +} + +// 'sect' + +export class Sect extends Section {}; + +// 'bksp' -- see 'tran' + +// 'elem' + +export class Elem extends Section { + strings: ElementString[] = []; + constructor(strs: Strs) { + super(); + this.strings.push(new ElementString(strs, '')); // C7043: null element string + } + allocElementString(strs: Strs, source: string, order?: string, tertiary?: string, tertiary_base?: string, prebase?: string): ElementString { + let s = new ElementString(strs, source, order, tertiary, tertiary_base, prebase); + let result = this.strings.find(item => item.isEqual(s)); + if(result === undefined) { + result = s; + this.strings.push(result); + } + return result; + } +}; + +// 'finl' -- see 'tran' + +// 'keys' is now `keys2.kmap` + +// 'loca' + +export class Loca extends Section { + locales: StrsItem[] = []; +}; + +// 'meta' + +export enum KeyboardSettings { + none = 0, + fallback = constants.meta_settings_fallback_omit, + transformFailure = constants.meta_settings_transformFailure_omit, + transformPartial = constants.meta_settings_transformPartial_hide, +}; + +export enum Meta_NormalizationForm { NFC='NFC', NFD='NFD', other='other' }; + +export class Meta extends Section { + author: StrsItem; + conform: StrsItem; + layout: StrsItem; + normalization: StrsItem; + indicator: StrsItem; + version: StrsItem; // semver version string, defaults to "0" + settings: KeyboardSettings; +}; + +// 'name' + +export class Name extends Section { + names: StrsItem[] = []; +}; + +// 'ordr' + +export class OrdrItem { + elements: ElementString; + before: ElementString; +}; + +export class Ordr extends Section { + items: OrdrItem[] = []; +}; + +// 'strs' + +/** + * A string item in memory. This will be replaced with an index + * into the string table at finalization. + */ +export class StrsItem { + readonly value: string; + constructor(value: string) { + this.value = value; + } + compareTo(o: StrsItem): number { + return StrsItem.binaryStringCompare(this.value, o.value); + } + static binaryStringCompare(a: string, b: string): number { + // https://tc39.es/ecma262/multipage/abstract-operations.html#sec-islessthan + if(typeof a != 'string' || typeof b != 'string') { + throw new Error('binaryStringCompare: inputs must be strings'); + } + if(a < b) return -1; + if(a > b) return 1; + return 0; + } +}; + +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 + * @returns + */ + allocAndUnescapeString(s?: string): StrsItem { + return this.allocString(unescapeString(s)); + } + /** + * Allocate a StrsItem given the string. + * @param s string + * @returns + */ + allocString(s?: string): StrsItem { + if(s === undefined || s === null) { + // undefined or null are always equivalent to empty string, see C7043 + s = ''; + } + + if(typeof s !== 'string') { + throw new Error('alloc_string: s must be a string, undefined, or null.'); + } + + let result = this.strings.find(item => item.value === s); + if(result === undefined) { + result = new StrsItem(s); + this.strings.push(result); + } + return result; + } +}; + +// 'tran' + +export enum TranItemFlags { + none = 0, + error = constants.tran_flags_error, +}; + +export class TranItem extends Section { + from: ElementString; + to: StrsItem; + before: ElementString; + flags: TranItemFlags; +}; + +export class Tran extends Section { + items: TranItem[] = []; + get id() { + return constants.section.tran; + } +}; + +// alias types for 'bksp', 'finl' + +export class Bksp extends Tran { + override get id() { + return constants.section.bksp; + } +}; +export class BkspItem extends TranItem {}; +export type BkspItemFlags = TranItemFlags; +export const BkspItemFlags = TranItemFlags; + +export class Finl extends Tran { + override get id() { + return constants.section.finl; + } +}; +export class FinlItem extends TranItem {}; +export type FinlItemFlags = TranItemFlags; +export const FinlItemFlags = TranItemFlags; + +// 'vkey' + +export class VkeyItem { + vkey: number; + target: number; +} + +export class Vkey extends Section { + vkeys: VkeyItem[] = []; +}; + +// 'disp' +export class DispItem { + to: StrsItem; + display: StrsItem; +}; + +export class Disp extends Section { + baseCharacter: StrsItem; + disps: DispItem[] = []; +}; + +// 'layr' + +/** + * In-memory `` + */ +export class LayrList { + hardware: number; + layers: LayrEntry[] = []; + minDeviceWidth: number; // millimeters +}; + +/** + * In-memory `` + */ + export class LayrEntry { + id: StrsItem; + mod: number; + rows: LayrRow[] = []; +}; + +/** + * In-memory `` + */ + export class LayrRow { + keys: StrsItem[] = []; +}; + +export class Layr extends Section { + lists: LayrList[] = []; +}; + +export class KeysKeys { + flags: number; + flicks: string; // for in-memory only + id: StrsItem; + longPress: ListItem; + longPressDefault: StrsItem; + multiTap: ListItem; + switch: StrsItem; + to: StrsItem; + width: number; +}; + + +export class KeysKmap { + vkey: number; + mod: number; + key: string; // for in-memory only +}; + +export class KeysFlicks { + flicks: KeysFlick[] = []; + id: StrsItem; + compareTo(b: KeysFlicks): number { + return this.id.compareTo(b.id); + } + constructor(id: StrsItem) { + this.id = id; + } +}; + +export class KeysFlick { + directions: ListItem; + flags: number; + to: StrsItem; +}; + +export class Keys extends Section { + keys: KeysKeys[] = []; + flicks: KeysFlicks[] = []; + kmap: KeysKmap[] = []; + constructor(strs: Strs) { + super(); + let nullFlicks = new KeysFlicks(strs.allocString('')); + this.flicks.push(nullFlicks); // C7043: null element string + } +}; + +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 + * @returns a List object + */ + allocListFromSpaces(strs: Strs, s?: string): 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)); + } + /** + * Return a List object referring to the string list. + * Note that a falsy list, or a list containing only an empty string + * `['']` will be stored as an empty list `[]`. + * @param strs Strs section for allocation + * @param s string list to allocate + * @returns + */ + allocList(strs: Strs, s?: string[]): ListItem { + // Special case the 'null' list for [] or [''] + if (!s || (s.length === 1 && s[0] === '')) { + return this.lists[0]; + } + let result = this.lists.find(item => item.isEqual(s)); + if(result === undefined) { + // allocate a new ListItem + result = new ListItem(strs, s); + this.lists.push(result); + } + return result; + } + constructor(strs: Strs) { + super(); + this.lists.push(new ListItem(strs, [])); // C7043: null element string + } + lists: ListItem[] = []; +}; + +export { ListItem as ListItem }; + +export interface KMXPlusData { + sect?: Strs; // sect is ignored in-memory + bksp?: Bksp; + disp?: Disp; + elem?: Elem; // elem is ignored in-memory + finl?: Finl; + keys?: Keys; + layr?: Layr; + list?: List; // list is ignored in-memory + loca?: Loca; + meta?: Meta; + name?: Name; + ordr?: Ordr; + strs?: Strs; // strs is ignored in-memory + tran?: Tran; + vkey?: Vkey; +}; + +export class KMXPlusFile extends KMXFile { + + /* KMXPlus file structures */ + + public readonly COMP_PLUS_SECT_ITEM: any; + public readonly COMP_PLUS_SECT: any; + + // COMP_PLUS_BKSP == COMP_PLUS_TRAN + public readonly COMP_PLUS_BKSP_ITEM: any; + public readonly COMP_PLUS_BKSP: any; + + public readonly COMP_PLUS_DISP_ITEM: any; + public readonly COMP_PLUS_DISP: any; + + public readonly COMP_PLUS_ELEM_ELEMENT: any; + public readonly COMP_PLUS_ELEM_STRING: any; + public readonly COMP_PLUS_ELEM: any; + + // COMP_PLUS_FINL == COMP_PLUS_TRAN + public readonly COMP_PLUS_FINL_ITEM: any; + public readonly COMP_PLUS_FINL: any; + + // COMP_PLUS_KEYS is now COMP_PLUS_KEYS_KMAP + + public readonly COMP_PLUS_LAYR_ENTRY: any; + public readonly COMP_PLUS_LAYR_KEY: any; + public readonly COMP_PLUS_LAYR_LIST: any; + public readonly COMP_PLUS_LAYR_ROW: any; + public readonly COMP_PLUS_LAYR: any; + + public readonly COMP_PLUS_KEYS_FLICK: any; + public readonly COMP_PLUS_KEYS_FLICKS: any; + public readonly COMP_PLUS_KEYS_KEY: any; + public readonly COMP_PLUS_KEYS_KMAP: any; + public readonly COMP_PLUS_KEYS: any; + + public readonly COMP_PLUS_LIST_LIST: any; + public readonly COMP_PLUS_LIST_INDEX: any; + public readonly COMP_PLUS_LIST: any; + + public readonly COMP_PLUS_LOCA_ITEM: any; + public readonly COMP_PLUS_LOCA: any; + + public readonly COMP_PLUS_META: any; + + public readonly COMP_PLUS_NAME_ITEM: any; + public readonly COMP_PLUS_NAME: any; + + public readonly COMP_PLUS_ORDR_ITEM: any; + public readonly COMP_PLUS_ORDR: any; + + public readonly COMP_PLUS_STRS_ITEM: any; + public readonly COMP_PLUS_STRS: any; + + public readonly COMP_PLUS_TRAN_ITEM: any; + public readonly COMP_PLUS_TRAN: any; + + public readonly COMP_PLUS_VKEY_ITEM: any; + public readonly COMP_PLUS_VKEY: any; + + /* File in-memory data */ + + public kmxplus: KMXPlusData = { }; + + constructor() { + super(); + + + // Binary-correct structures matching kmx_plus.h + + // 'sect' + + this.COMP_PLUS_SECT_ITEM = new r.Struct({ + sect: r.uint32le, + offset: r.uint32le //? new r.VoidPointer(r.uint32le, {type: 'global'}) + }); + + this.COMP_PLUS_SECT = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + total: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_SECT_ITEM, 'count') + }); + + // 'bksp' - see 'tran' + + // 'disp' + this.COMP_PLUS_DISP_ITEM = new r.Struct({ + to: r.uint32le, + display: r.uint32le, + }); + + this.COMP_PLUS_DISP = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + count: r.uint32le, + baseCharacter: r.uint32le, + items: new r.Array(this.COMP_PLUS_DISP_ITEM, 'count'), + }); + + // 'elem' + + this.COMP_PLUS_ELEM_ELEMENT = new r.Struct({ + element: r.uint32le, + flags: r.uint32le + }); + + this.COMP_PLUS_ELEM_STRING = new r.Struct({ + offset: r.uint32le, + length: r.uint32le + }); + + this.COMP_PLUS_ELEM = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + count: r.uint32le, + strings: new r.Array(this.COMP_PLUS_ELEM_STRING, 'count') + // + variable subtable: Element data (see KMXPlusBuilder.emitElements()) + }); + + // 'finl' - see 'tran' + + // 'keys' - see 'keys.kmap' + + // 'layr' + + this.COMP_PLUS_LAYR_ENTRY = new r.Struct({ + id: r.uint32le, // str + mod: r.uint32le, // bitfield + row: r.uint32le, // index into rows + count: r.uint32le, + }); + + this.COMP_PLUS_LAYR_KEY = new r.Struct({ + key: r.uint32le, // str: key id + }); + + this.COMP_PLUS_LAYR_LIST = new r.Struct({ + hardware: r.uint32le, //enum + layer: r.uint32le, // index into layers + count: r.uint32le, + minDeviceWidth: r.uint32le, // integer: millimeters + }); + + this.COMP_PLUS_LAYR_ROW = new r.Struct({ + key: r.uint32le, + count: r.uint32le, + }); + + this.COMP_PLUS_LAYR = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + listCount: r.uint32le, + layerCount: r.uint32le, + rowCount: r.uint32le, + keyCount: r.uint32le, + lists: new r.Array(this.COMP_PLUS_LAYR_LIST, 'listCount'), + layers: new r.Array(this.COMP_PLUS_LAYR_ENTRY, 'layerCount'), + rows: new r.Array(this.COMP_PLUS_LAYR_ROW, 'rowCount'), + keys: new r.Array(this.COMP_PLUS_LAYR_KEY, 'keyCount'), + }); + + this.COMP_PLUS_KEYS_FLICK = new r.Struct({ + directions: r.uint32le, // list + flags: r.uint32le, + to: r.uint32le, // str | codepoint + }); + + this.COMP_PLUS_KEYS_FLICKS = new r.Struct({ + count: r.uint32le, + flick: r.uint32le, + id: r.uint32le, // str + }); + + this.COMP_PLUS_KEYS_KEY = new r.Struct({ + to: r.uint32le, // str | codepoint + flags: r.uint32le, + id: r.uint32le, // str + switch: r.uint32le, // str + width: r.uint32le, // width*10 ( 1 = 0.1 keys) + longPress: r.uint32le, // list index + longPressDefault: r.uint32le, // str + multiTap: r.uint32le, // list index + flicks: r.uint32le, // index into flicks table + }); + + this.COMP_PLUS_KEYS_KMAP = new r.Struct({ + vkey: r.uint32le, + mod: r.uint32le, + key: r.uint32le, // index into 'keys' subtable + }); + + this.COMP_PLUS_KEYS = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + keyCount: r.uint32le, + flicksCount: r.uint32le, + flickCount: r.uint32le, + kmapCount: r.uint32le, + keys: new r.Array(this.COMP_PLUS_KEYS_KEY, 'keyCount'), + flicks: new r.Array(this.COMP_PLUS_KEYS_FLICKS, 'flicksCount'), + flick: new r.Array(this.COMP_PLUS_KEYS_FLICK, 'flickCount'), + kmap: new r.Array(this.COMP_PLUS_KEYS_KMAP, 'kmapCount'), + }); + + // 'list' + + this.COMP_PLUS_LIST_LIST = new r.Struct({ + index: r.uint32le, + count: r.uint32le, + }); + + this.COMP_PLUS_LIST_INDEX = new r.Struct({ + str: r.uint32le, // str + }); + + this.COMP_PLUS_LIST = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + listCount: r.uint32le, + indexCount: r.uint32le, + lists: new r.Array(this.COMP_PLUS_LIST_LIST, 'listCount'), + indices: new r.Array(this.COMP_PLUS_LIST_INDEX, 'indexCount'), + }); + + // 'loca' + + this.COMP_PLUS_LOCA_ITEM = r.uint32le; //str + + this.COMP_PLUS_LOCA = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_LOCA_ITEM, 'count') + }); + + // 'meta' + + this.COMP_PLUS_META = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + author: r.uint32le, //str + conform: r.uint32le, //str + layout: r.uint32le, //str + normalization: r.uint32le, //str + indicator: r.uint32le, //str + version: r.uint32le, //str + settings: r.uint32le, //new r.Bitfield(r.uint32le, ['fallback', 'transformFailure', 'transformPartial']) + }); + + // 'name' + + this.COMP_PLUS_NAME_ITEM = r.uint32le; //str + + this.COMP_PLUS_NAME = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_NAME_ITEM, 'count') + }); + + // 'ordr' + + this.COMP_PLUS_ORDR_ITEM = new r.Struct({ + elements: r.uint32le, //elem + before: r.uint32le //elem + }); + + this.COMP_PLUS_ORDR = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_ORDR_ITEM, 'count') + }); + + // 'strs' + + this.COMP_PLUS_STRS_ITEM = new r.Struct({ + // While we use length which is number of utf-16 code units excluding null terminator, + // we always write a null terminator, so we can get restructure to do that for us here + offset: r.uint32le, //? new r.Pointer(r.uint32le, new r.String(null, 'utf16le')), + length: r.uint32le + }); + + this.COMP_PLUS_STRS = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_STRS_ITEM, 'count') + // + variable subtable: String data (see KMXPlusBuilder.emitStrings()) + }); + + // 'tran' + + this.COMP_PLUS_TRAN_ITEM = new r.Struct({ + from: r.uint32le, //elem + to: r.uint32le, //str + before: r.uint32le, //elem + flags: r.uint32le //bitfield + }); + + this.COMP_PLUS_TRAN = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_TRAN_ITEM, 'count') + }); + + // 'vkey' + + this.COMP_PLUS_VKEY_ITEM = new r.Struct({ + vkey: r.uint32le, + target: r.uint32le + }); + + this.COMP_PLUS_VKEY = new r.Struct({ + ident: r.uint32le, + size: r.uint32le, + count: r.uint32le, + items: new r.Array(this.COMP_PLUS_VKEY_ITEM, 'count') + }); + + // Aliases + + this.COMP_PLUS_BKSP_ITEM = this.COMP_PLUS_TRAN_ITEM; + this.COMP_PLUS_FINL_ITEM = this.COMP_PLUS_TRAN_ITEM; + this.COMP_PLUS_BKSP = this.COMP_PLUS_TRAN; + this.COMP_PLUS_FINL = this.COMP_PLUS_TRAN; + } +} diff --git a/common/web/types/src/kmx/kmx.ts b/common/web/types/src/kmx/kmx.ts new file mode 100644 index 00000000000..0c7c05aa08c --- /dev/null +++ b/common/web/types/src/kmx/kmx.ts @@ -0,0 +1,360 @@ +import * as r from 'restructure'; + +/* Definitions from kmx_file.h. Must be kept in sync */ + +// TODO: split kmx-file from kmx in-memory, similar to what I've done for kvk (keep restructure decl + BUILDER_ interfaces together) + +// In memory representations of KMX structures +// kmx-builder will transform these to the corresponding COMP_xxxx + +export class KEYBOARD { + //TODO: additional header fields + groups: GROUP[]; + stores: STORE[]; +}; + +export class STORE { + dwSystemID: number; + dpName: string; + dpString: string; +}; + +export class GROUP { + dpName: string; + keys: KEY[]; + dpMatch: string; + dpNoMatch: string; + fUsingKeys: boolean; +}; + +export class KEY { + Key: number; + Line: number; + ShiftFlags: number; + dpOutput: string; + dpContext: string; +}; + + +export class KMXFile { + + /* KMX file structures */ + + public readonly COMP_STORE: any; + public readonly COMP_KEY: any; + public readonly COMP_GROUP: any; + public readonly COMP_KEYBOARD_KMXPLUSINFO: any; + public readonly COMP_KEYBOARD: any; + + public static readonly FILEID_COMPILED = 0x5354584B; // 'KXTS' + + // + // File version identifiers (COMP_KEYBOARD.dwFileVersion) + // + + public static readonly VERSION_30 = 0x00000300; + public static readonly VERSION_31 = 0x00000301; + public static readonly VERSION_32 = 0x00000302; + public static readonly VERSION_40 = 0x00000400; + public static readonly VERSION_50 = 0x00000500; + public static readonly VERSION_501 = 0x00000501; + public static readonly VERSION_60 = 0x00000600; + public static readonly VERSION_70 = 0x00000700; + public static readonly VERSION_80 = 0x00000800; + public static readonly VERSION_90 = 0x00000900; + public static readonly VERSION_100 = 0x00000A00; + public static readonly VERSION_140 = 0x00000E00; + public static readonly VERSION_150 = 0x00000F00; + + public static readonly VERSION_160 = 0x00001000; + + public static readonly VERSION_MIN = this.VERSION_50; + public static readonly VERSION_MAX = this.VERSION_160; + + // + // Backspace types + // + + public static readonly BK_DEFAULT = 0; + public static readonly BK_DEADKEY = 1; + + // Different begin types (COMP_STORE.StartGroup_*) + + public static readonly BEGIN_ANSI = 0; + public static readonly BEGIN_UNICODE = 1; + + // + // System Store values (COMP_STORE.dwSystemID) + // + + public static readonly TSS_NONE = 0; + public static readonly TSS_BITMAP = 1; + public static readonly TSS_COPYRIGHT = 2; + public static readonly TSS_HOTKEY = 3; + public static readonly TSS_LANGUAGE = 4; + public static readonly TSS_LAYOUT = 5; + public static readonly TSS_MESSAGE = 6; + public static readonly TSS_NAME = 7; + public static readonly TSS_VERSION = 8; + public static readonly TSS_CAPSONONLY = 9; + public static readonly TSS_CAPSALWAYSOFF = 10; + public static readonly TSS_SHIFTFREESCAPS = 11; + public static readonly TSS_LANGUAGENAME = 12; + + public static readonly TSS_CALLDEFINITION = 13; + public static readonly TSS_CALLDEFINITION_LOADFAILED = 14; + + public static readonly TSS_ETHNOLOGUECODE = 15; + + public static readonly TSS_DEBUG_LINE = 16; + + public static readonly TSS_MNEMONIC = 17; + + public static readonly TSS_INCLUDECODES = 18; + + public static readonly TSS_OLDCHARPOSMATCHING = 19; + + public static readonly TSS_COMPILEDVERSION = 20; + public static readonly TSS_KEYMANCOPYRIGHT = 21; + + public static readonly TSS_CUSTOMKEYMANEDITION = 22; + public static readonly TSS_CUSTOMKEYMANEDITIONNAME = 23; + + /* Keyman 7.0 system stores */ + + public static readonly TSS__KEYMAN_60_MAX = 23; + + public static readonly TSS_VISUALKEYBOARD = 24; + public static readonly TSS_KMW_RTL = 25; + public static readonly TSS_KMW_HELPFILE = 26; + public static readonly TSS_KMW_HELPTEXT = 27; + public static readonly TSS_KMW_EMBEDJS = 28; + + public static readonly TSS_WINDOWSLANGUAGES = 29; + + public static readonly TSS__KEYMAN_70_MAX = 29; + + /* Keyman 8.0 system stores */ + + public static readonly TSS_COMPARISON = 30; + + public static readonly TSS__KEYMAN_80_MAX = 30; + + /* Keyman 9.0 system stores */ + + public static readonly TSS_PLATFORM = 31; + public static readonly TSS_BASELAYOUT = 32; + public static readonly TSS_LAYER = 33; + + public static readonly TSS_PLATFORM_NOMATCH = 0x8001; // Reserved for internal use - after platform statement is run, set to either TSS_PLATFORM_NOMATCH or TSS_PLATFORM_MATCH + public static readonly TSS_PLATFORM_MATCH = 0x8002; // Reserved for internal use - as the result will never change for the lifetime of the process. + + public static readonly TSS_VKDICTIONARY = 34; // Dictionary of virtual key names for v9 dynamic layouts + public static readonly TSS_LAYOUTFILE = 35; // Keyman 9 layer-based JSON OSK + public static readonly TSS_KEYBOARDVERSION = 36; // &keyboardversion system store // I4140 + public static readonly TSS_KMW_EMBEDCSS = 37; + + public static readonly TSS_TARGETS = 38; + + public static readonly TSS__KEYMAN_90_MAX = 38; + + /* Keyman 14.0 system stores */ + + public static readonly TSS_CASEDKEYS = 39; + + public static readonly TSS__KEYMAN_140_MAX = 39; + + /* Keyman 15.0 system stores */ + + public static readonly TSS_BEGIN_NEWCONTEXT = 40; + public static readonly TSS_BEGIN_POSTKEYSTROKE = 41; + public static readonly TSS_NEWLAYER = 42; + public static readonly TSS_OLDLAYER = 43; + + public static readonly TSS__KEYMAN_150_MAX = 43; + + public static readonly TSS__MAX = 43; + + + public static readonly UC_SENTINEL = 0xFFFF; + public static readonly UC_SENTINEL_EXTENDEDEND = 0x10; + + public static readonly U_UC_SENTINEL = "\uFFFF"; + + // + // VK__MAX defines the highest virtual key code defined in the system = 0xFF. Custom VK codes start at 256 + // + + public static readonly VK__MAX = 255; + + // + // Extended String CODE_ values + // + + public static readonly CODE_ANY = 0x01; + public static readonly CODE_INDEX = 0x02; + public static readonly CODE_CONTEXT = 0x03; + public static readonly CODE_NUL = 0x04; + public static readonly CODE_USE = 0x05; + public static readonly CODE_RETURN = 0x06; + public static readonly CODE_BEEP = 0x07; + public static readonly CODE_DEADKEY = 0x08; + // 0x09 = bkspace.-- we don't need to keep this separate though with UC_SENTINEL + public static readonly CODE_EXTENDED = 0x0A; + //public static readonly CODE_EXTENDEDEND = 0x0B; deprecated + public static readonly CODE_SWITCH = 0x0C; + public static readonly CODE_KEY = 0x0D; + public static readonly CODE_CLEARCONTEXT = 0x0E; + public static readonly CODE_CALL = 0x0F; + // UC_SENTINEL_EXTENDEDEND 0x10 + public static readonly CODE_CONTEXTEX = 0x11; + + public static readonly CODE_NOTANY = 0x12; + + public static readonly CODE_KEYMAN70_LASTCODE = 0x12; + + public static readonly CODE_SETOPT = 0x13; + public static readonly CODE_IFOPT = 0x14; + public static readonly CODE_SAVEOPT = 0x15; + public static readonly CODE_RESETOPT = 0x16; + + public static readonly CODE_KEYMAN80_LASTCODE = 0x16; + + /* Keyman 9.0 codes */ + + public static readonly CODE_IFSYSTEMSTORE = 0x17; + public static readonly CODE_SETSYSTEMSTORE = 0x18; + + public static readonly CODE_LASTCODE = 0x18; + + + public static readonly KF_SHIFTFREESCAPS = 0x0001; + public static readonly KF_CAPSONONLY = 0x0002; + public static readonly KF_CAPSALWAYSOFF = 0x0004; + public static readonly KF_LOGICALLAYOUT = 0x0008; + public static readonly KF_AUTOMATICVERSION = 0x0010; + + // 16.0: Support for LDML Keyboards in KMXPlus file format + public static readonly KF_KMXPLUS = 0x0020; + + public static readonly HK_ALT = 0x00010000; + public static readonly HK_CTRL = 0x00020000; + public static readonly HK_SHIFT = 0x00040000; + + public static readonly LCTRLFLAG = 0x0001; // Left Control flag + public static readonly RCTRLFLAG = 0x0002; // Right Control flag + public static readonly LALTFLAG = 0x0004; // Left Alt flag + public static readonly RALTFLAG = 0x0008; // Right Alt flag + public static readonly K_SHIFTFLAG = 0x0010; // Either shift flag + public static readonly K_CTRLFLAG = 0x0020; // Either ctrl flag + public static readonly K_ALTFLAG = 0x0040; // Either alt flag + //public static readonly K_METAFLAG = 0x0080; // Either Meta-key flag (tentative). Not usable in keyboard rules; + // Used internally (currently, only by KMW) to ensure Meta-key + // shortcuts safely bypass rules + // Meta key = Command key on macOS, Windows key on Windows + public static readonly CAPITALFLAG = 0x0100; // Caps lock on + public static readonly NOTCAPITALFLAG = 0x0200; // Caps lock NOT on + public static readonly NUMLOCKFLAG = 0x0400; // Num lock on + public static readonly NOTNUMLOCKFLAG = 0x0800; // Num lock NOT on + public static readonly SCROLLFLAG = 0x1000; // Scroll lock on + public static readonly NOTSCROLLFLAG = 0x2000; // Scroll lock NOT on + public static readonly ISVIRTUALKEY = 0x4000; // It is a Virtual Key Sequence + public static readonly VIRTUALCHARKEY = 0x8000; // Keyman 6.0: Virtual Key Cap Sequence NOT YET + + public static readonly K_MODIFIERFLAG = 0x007F; + public static readonly K_NOTMODIFIERFLAG = 0xFF00; // I4548 + + public static readonly COMP_KEYBOARD_SIZE = 64; + public static readonly COMP_KEYBOARD_KMXPLUSINFO_SIZE = 8; + public static readonly COMP_STORE_SIZE = 12; + public static readonly COMP_GROUP_SIZE = 24; + public static readonly COMP_KEY_SIZE = 20; + + /* In-memory representation of the keyboard */ + + public keyboard: KEYBOARD = { + groups: [], + stores: [] + }; + + constructor() { + + // Binary-correct structures matching kmx_file.h + + this.COMP_STORE = new r.Struct({ + dwSystemID: r.uint32le, + dpName: r.uint32le, + dpString: r.uint32le + }); + + if(this.COMP_STORE.size() != KMXFile.COMP_STORE_SIZE) { + throw "COMP_STORE size is "+this.COMP_STORE.size()+" but should be "+KMXFile.COMP_STORE_SIZE+" bytes"; + } + + this.COMP_KEY = new r.Struct({ + Key: r.uint16le, + _padding: new r.Reserved(r.uint16le), // padding + Line: r.uint32le, + ShiftFlags: r.uint32le, + dpOutput: r.uint32le, + dpContext: r.uint32le + }); + + if(this.COMP_KEY.size() != KMXFile.COMP_KEY_SIZE) { + throw "COMP_KEY size is "+this.COMP_KEY.size()+" but should be "+KMXFile.COMP_KEY_SIZE+" bytes"; + } + + this.COMP_GROUP = new r.Struct({ + dpName: r.uint32le, + dpKeyArray: r.uint32le, // [LPKEY] address of first item in key array + dpMatch: r.uint32le, + dpNoMatch: r.uint32le, + cxKeyArray: r.uint32le, // in array entries + fUsingKeys: r.uint32le // group(xx) [using keys] <-- specified or not + }); + + if(this.COMP_GROUP.size() != KMXFile.COMP_GROUP_SIZE) { + throw "COMP_GROUP size is "+this.COMP_GROUP.size()+" but should be "+KMXFile.COMP_GROUP_SIZE+" bytes"; + } + + this.COMP_KEYBOARD_KMXPLUSINFO = new r.Struct({ + dpKMXPlus: r.uint32le, // 0040 offset of KMXPlus data, header is first + dwKMXPlusSize: r.uint32le // 0044 size in bytes of entire KMXPlus data + }); + + if(this.COMP_KEYBOARD_KMXPLUSINFO.size() != KMXFile.COMP_KEYBOARD_KMXPLUSINFO_SIZE) { + throw "COMP_KEYBOARD_KMXPLUSINFO size is "+this.COMP_KEYBOARD_KMXPLUSINFO.size()+" but should be "+KMXFile.COMP_KEYBOARD_KMXPLUSINFO_SIZE+" bytes"; + } + + this.COMP_KEYBOARD = new r.Struct({ + dwIdentifier: r.uint32le, // 0000 Keyman compiled keyboard id + + dwFileVersion: r.uint32le, // 0004 Version of the file - Keyman 4.0 is 0x0400 + + dwCheckSum: r.uint32le, // 0008 As stored in keyboard + KeyboardID: r.uint32le, // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts + IsRegistered: r.uint32le, // 0010 + version: r.uint32le, // 0014 keyboard version + + cxStoreArray: r.uint32le, // 0018 in array entries + cxGroupArray: r.uint32le, // 001C in array entries + + dpStoreArray: r.uint32le, // 0020 [LPSTORE] address of first item in store array + dpGroupArray: r.uint32le, // 0024 [LPGROUP] address of first item in group array + + StartGroup_ANSI: r.uint32le, // 0028 index of starting ANSI group + StartGroup_Unicode: r.uint32le, // 0028 index of starting Unicode groups + + dwFlags: r.uint32le, // 0030 Flags for the keyboard file + + dwHotKey: r.uint32le, // 0034 standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) + + dpBitmapOffset: r.uint32le, // 0038 offset of the bitmaps in the file + dwBitmapSize: r.uint32le // 003C size in bytes of the bitmaps + }); + + if(this.COMP_KEYBOARD.size() != KMXFile.COMP_KEYBOARD_SIZE) { + throw "COMP_KEYBOARD size is "+this.COMP_KEYBOARD.size()+" but should be "+KMXFile.COMP_KEYBOARD_SIZE+" bytes"; + } + } +} \ No newline at end of file diff --git a/common/web/types/src/kmx/string-list.ts b/common/web/types/src/kmx/string-list.ts new file mode 100644 index 00000000000..4b5716b85d7 --- /dev/null +++ b/common/web/types/src/kmx/string-list.ts @@ -0,0 +1,74 @@ +import { Strs, StrsItem } from './kmx-plus.js'; + +/** + * A single entry in a ListItem. + * Contains a StrsItem as its value. + */ +export class ListIndex { + readonly value: StrsItem; // will become index into Strs table + constructor(value: StrsItem) { + this.value = value; + } + isEqual(a: ListIndex | string) { + // so we can compare this to a string + return a.toString() === this.toString(); + } + toString(): string { + return this.value.value; + } +}; + +/** + * A string list in memory. This will be replaced with an index + * into the string table at finalization. + */ +export class ListItem extends Array { + /** + * 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 + * @returns + */ + constructor(strs: Strs, source: Array) { + super(); + if(!source) { + return; + } + + for (const str of source) { + let index = new ListIndex(strs.allocString(str)); + this.push(index); + } + } + isEqual(a: ListItem | string[]): boolean { + if (a.length != this.length) { + return false; + } + for (let i = 0; i < a.length; i++) { + if (!this[i].isEqual(a[i])) { + return false; + } + } + return true; + } + compareTo(o: ListItem): number { + for (let i = 0; i < Math.min(this.length, o.length); i++) { + const r = this[i].value.compareTo(o[i].value); + if (r !== 0) { + return r; + } + } + // prefix is the same, so go by length: shortest is first. + if (this.length < o.length) { + return -1; + } else if (this.length > o.length) { + return 1; + } else { + return 0; + } + } + toString(): string { + return this.map(v => v.value.value).join(' '); + } +}; diff --git a/common/web/types/src/kpj/keyman-developer-project.ts b/common/web/types/src/kpj/keyman-developer-project.ts new file mode 100644 index 00000000000..898c6f2b3f8 --- /dev/null +++ b/common/web/types/src/kpj/keyman-developer-project.ts @@ -0,0 +1,155 @@ +// +// Version 1.0 of Keyman Developer Project .kpj file +// + +import * as path from 'path'; +import * as fs from 'fs'; + +export class KeymanDeveloperProject { + options: KeymanDeveloperProjectOptions; + files: KeymanDeveloperProjectFile[]; + constructor(version: KeymanDeveloperProjectVersion) { + this.options = new KeymanDeveloperProjectOptions(version); + this.files = []; + } + /** + * Adds .kmn, .xml, .kps to project based on options.sourcePath + * @param projectPath Full path to project.kpj (even if the file doesn't exist) + */ + populateFiles(projectPath: string) { + if(this.options.version != '2.0') { + throw new Error('populateFiles can only be called on a v2.0 project'); + } + + let sourcePath = this.resolveProjectPath(projectPath, this.options.sourcePath); + let files = fs.readdirSync(sourcePath); + for(let filename of files) { + let fullPath = path.join(sourcePath, filename); + if(filename.match(/\.xml$/i)) { + if(!fs.readFileSync(fullPath, 'utf-8').match(/ldmlKeyboard\.dtd/)) { + // Skip this .xml because we assume it isn't really a keyboard .xml + continue; + } + } + if(filename.match(/\.(kmn|kps|xml|model\.ts)$/i)) { + let file = new KeymanDeveloperProjectFile20(fullPath); + this.files.push(file); + } + } + } + + resolveProjectPath(projectPath: string, p: string): string { + // Replace placeholders in the target path + return p.replace('$PROJECTPATH', path.dirname(projectPath)); + } + + resolveInputFilePath(projectPath: string, file: KeymanDeveloperProjectFile): string { + let p = path.dirname(projectPath); + return path.normalize(path.resolve(p, file.filePath)); + } + + resolveOutputFilePath(projectPath: string, file: KeymanDeveloperProjectFile, sourceExt: string, targetExt: string): string { + // Roughly corresponds to Delphi TProject.GetTargetFileName + let p = this.options.version == '1.0' ? + this.options.buildPath || '$SOURCEPATH' : + this.options.buildPath; + + // 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', path.dirname(this.resolveInputFilePath(projectPath, file))); + } + + p = this.resolveProjectPath(projectPath, p); + + let f = file.filename.replace(new RegExp(`\\${sourceExt}$`, 'i'), targetExt); + return path.normalize(path.join(p, f)); + } + +}; + +export enum KeymanDeveloperProjectType { + Keyboard, + LexicalModel +}; + +export type KeymanDeveloperProjectVersion = + "1.0" | // Keyman Developer <17.0: All files referenced in .kpj + "2.0"; // Keyman Developer 17.0+: Files in sub-folders implicitly included + +export class KeymanDeveloperProjectOptions { + buildPath: string; + sourcePath: string; + compilerWarningsAsErrors: boolean = false; + warnDeprecatedCode: boolean = true; + checkFilenameConventions: boolean = true; + projectType: KeymanDeveloperProjectType = KeymanDeveloperProjectType.Keyboard; + readonly version: KeymanDeveloperProjectVersion; + constructor(version: KeymanDeveloperProjectVersion) { + this.version = version; + switch(version) { + case "1.0": + this.buildPath = ''; + this.sourcePath = ''; + break; + case "2.0": + this.buildPath = '$PROJECTPATH/build'; + this.sourcePath = '$PROJECTPATH/source'; + break; + default: + throw new Error('Invalid version'); + } + } +}; + +export type KeymanDeveloperProjectFile = KeymanDeveloperProjectFile10 | KeymanDeveloperProjectFile20; + +export class KeymanDeveloperProjectFile10 { + readonly id: string; // 1.0 only + readonly filename: string; + readonly filePath: string; + readonly fileVersion: string; // 1.0 only + readonly fileType: string; // file extension of filename, but .model.ts is technically not the ext because of 2 periods + details: KeymanDeveloperProjectFileDetail_Kmn & KeymanDeveloperProjectFileDetail_Kps; // 1.0 only + childFiles: KeymanDeveloperProjectFile[]; // 1.0 only + constructor(id: string, filename: string, filePath: string, fileVersion:string, fileType: string) { + this.details = {}; + this.childFiles = []; + this.id = id; + this.filename = filename; + this.filePath = filePath; + this.fileVersion = fileVersion; + this.fileType = fileType; + } +}; + +export type KeymanDeveloperProjectFileType20 = '.model.ts' | '.kmn' | '.xml' | '.kps'; + +export class KeymanDeveloperProjectFile20 { + readonly filename: string; + readonly filePath: string; + readonly fileType: string; // file extension of filename, but .model.ts is technically not the ext because of 2 periods + constructor(filePath: string) { + this.filename = path.basename(filePath); + this.filePath = filePath; + if(this.filename.match(/\.model\.ts$/)) { + // .model.ts is a bit of a hassle... + this.fileType = '.model.ts'; + } else { + this.fileType = path.extname(this.filename); + } + } +}; + +export class KeymanDeveloperProjectFileDetail_Kps { + name?: string; + copyright?: string; + version?: string; +}; + +export class KeymanDeveloperProjectFileDetail_Kmn { + name?: string; + copyright?: string; + message?: string; +}; diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts new file mode 100644 index 00000000000..121207acb9c --- /dev/null +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -0,0 +1,102 @@ +import * as xml2js from 'xml2js'; +import { KPJFile, KPJFileProject } from './kpj-file.js'; +import Ajv from 'ajv'; +import { boxXmlArray } from '../util/util.js'; +import { KeymanDeveloperProject, KeymanDeveloperProjectFile10, KeymanDeveloperProjectType } from './keyman-developer-project.js'; + +export class KPJFileReader { + public read(file: Uint8Array): KPJFile { + let data: KPJFile; + + const parser = new xml2js.Parser({ + explicitArray: false, + mergeAttrs: false, + includeWhiteChars: false, + normalize: false, + emptyTag: '' + }); + + parser.parseString(file, (e: unknown, r: unknown) => { data = r as KPJFile }); + data = this.boxArrays(data); + return data as KPJFile; + } + + public validate(source: KPJFile, schemaBuffer: Buffer): void { + const schema = JSON.parse(schemaBuffer.toString('utf8')); + const ajv = new Ajv(); + if(!ajv.validate(schema, source)) { + throw new Error(ajv.errorsText()); + } + } + + private boolFromString(value: string, def: boolean) { + value = (value || '').toLowerCase(); + if(value === 'true') return true; + if(value === 'false') return false; + return def; + } + + public transform(projectPath: string, source: KPJFile): KeymanDeveloperProject { + // NOTE: at this point, the xml should have been validated + // and matched the schema result so we can assume the source + // is a valid shape + let project = source.KeymanDeveloperProject; + let result: KeymanDeveloperProject = new KeymanDeveloperProject(project.Options?.Version || "1.0"); + 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, '/'); + } else { + result.options.buildPath = (project.Options?.BuildPath || '').replace(/\\/g, '/'); + } + result.options.checkFilenameConventions = this.boolFromString(project.Options?.CheckFilenameConventions, true); + result.options.compilerWarningsAsErrors = this.boolFromString(project.Options?.CompilerWarningsAsErrors, false); + result.options.warnDeprecatedCode = this.boolFromString(project.Options?.WarnDeprecatedCode, true); + result.options.projectType = + project.Options?.ProjectType == 'keyboard' ? KeymanDeveloperProjectType.Keyboard : + project.Options?.ProjectType == 'lexicalmodel' ? KeymanDeveloperProjectType.LexicalModel : + KeymanDeveloperProjectType.Keyboard; // Default is keyboard if missing + + if(result.options.version == '1.0') { + this.transformFilesVersion10(project, result); + } else { + result.populateFiles(projectPath); + } + + return result; + } + + private transformFilesVersion10(project: KPJFileProject, result: KeymanDeveloperProject) { + let ids: { [id: string]: KeymanDeveloperProjectFile10; } = {}; + for (let sourceFile of project.Files?.File) { + let file: KeymanDeveloperProjectFile10 = new KeymanDeveloperProjectFile10( + sourceFile.ID || '', + sourceFile.Filename || '', + (sourceFile.Filepath || '').replace(/\\/g, '/'), + sourceFile.FileVersion || '', + sourceFile.FileType || '' + ); + if (sourceFile.Details) { + file.details.copyright = sourceFile.Details.Copyright; + file.details.name = sourceFile.Details.Name; + file.details.message = sourceFile.Details.Message; + file.details.version = sourceFile.Details.Version; + } + if (sourceFile.ParentFileID && ids[sourceFile.ParentFileID]) { + ids[sourceFile.ParentFileID].childFiles.push(file); + } else { + result.files.push(file); + ids[file.id] = file; + } + } + } + + /** + * xml2js will not place single-entry objects into arrays. + * Easiest way to fix this is to box them ourselves as needed + * @param source KVKSourceFile + */ + private boxArrays(source: KPJFile) { + boxXmlArray(source.KeymanDeveloperProject?.Files, 'File'); + return source; + } +} \ No newline at end of file diff --git a/common/web/types/src/kpj/kpj-file.ts b/common/web/types/src/kpj/kpj-file.ts new file mode 100644 index 00000000000..3de41f7619c --- /dev/null +++ b/common/web/types/src/kpj/kpj-file.ts @@ -0,0 +1,52 @@ +/* + * Keyman Developer Project File + */ + + +// These interfaces match XML read by xml2js. Use `KeymanDeveloperProject` +// returned by `KPJFileReader.transform()` for all processing. + +export interface KPJFile { + KeymanDeveloperProject: KPJFileProject; +} + +export interface KPJFileProject { + Options?: KPJFileOptions; // Required + Files?: KPJFileFiles; // Required in 1.0, optional and always ignored in 2.0 +}; + +export interface KPJFileOptions { + BuildPath?: string; // default '' in 1.0, '$PROJECTPATH/build' in 2.0 + SourcePath?: string; // default '' in 1.0, '$PROJECTPATH/source' in 2.0 + CompilerWarningsAsErrors?: string; // default False + WarnDeprecatedCode?: string; // default True + CheckFilenameConventions?: string; // default True + ProjectType?: 'keyboard' | 'lexicalmodel'; // default 'keyboard' + Version?: '1.0' | '2.0'; // default 1.0 +}; + +export interface KPJFileFiles { + File?: KPJFileFile[]; +}; + +export interface KPJFileFile { + ID?: string; + Filename?: string; + Filepath?: string; + FileVersion?: string; + FileType?: string; + Details?: KPJFileFileDetail_Kmn & KPJFileFileDetail_Kps; + ParentFileID?: string; +}; + +export interface KPJFileFileDetail_Kmn { + Name?: string; + Copyright?: string; + Message?: string; +}; + +export interface KPJFileFileDetail_Kps { + Name?: string; + Copyright?: string; + Version?: string; +}; diff --git a/common/web/types/src/kvk/kvk-file-reader.ts b/common/web/types/src/kvk/kvk-file-reader.ts new file mode 100644 index 00000000000..c3ee8924506 --- /dev/null +++ b/common/web/types/src/kvk/kvk-file-reader.ts @@ -0,0 +1,34 @@ +import KVKFile, { BUILDER_KVK_FILE, BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_HEADER_VERSION } from "./kvk-file.js"; +import { VisualKeyboard, VisualKeyboardKey } from "./visual-keyboard.js"; + +export default class KvkFileReader { + public read(source: Uint8Array): VisualKeyboard { + let binary: BUILDER_KVK_FILE; + let kvk = new KVKFile(); + binary = kvk.KVK_FILE.fromBuffer(source); + if(binary.header.identifier != BUILDER_KVK_HEADER_IDENTIFIER || + binary.header.version != BUILDER_KVK_HEADER_VERSION) { + return null; + } + let result = new VisualKeyboard(); + result.header.version = binary.header.version; + result.header.flags = binary.header.flags; + result.header.associatedKeyboard = binary.header.associatedKeyboard.str; + result.header.ansiFont.color = binary.header.ansiFont.color; + result.header.ansiFont.name = binary.header.ansiFont.name.str; + result.header.ansiFont.size = binary.header.ansiFont.size; + result.header.unicodeFont.color = binary.header.unicodeFont.color; + result.header.unicodeFont.name = binary.header.unicodeFont.name.str; + result.header.unicodeFont.size = binary.header.unicodeFont.size; + for(let binaryKey of binary.keys) { + let key: VisualKeyboardKey = { + flags: binaryKey.flags, + vkey: binaryKey.vkey, + shift: binaryKey.shift, + text: binaryKey.text.str + }; + result.keys.push(key); + } + return result; + } +}; \ No newline at end of file diff --git a/common/web/types/src/kvk/kvk-file-writer.ts b/common/web/types/src/kvk/kvk-file-writer.ts new file mode 100644 index 00000000000..c2d65d7f260 --- /dev/null +++ b/common/web/types/src/kvk/kvk-file-writer.ts @@ -0,0 +1,72 @@ +import KVKFile, { BUILDER_KVK_FILE, BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_HEADER_VERSION, BUILDER_KVK_KEY, BUILDER_KVK_STRING } from "./kvk-file.js"; +import { VisualKeyboard } from "./visual-keyboard.js"; + +export default class KvkFileWriter { + /** + * Writes the visual keyboard to a binary .kvk format byte array. + * @param source VisualKeyboard + * @returns Uint8Array, the .kvk file + */ + write(source: VisualKeyboard): Uint8Array { + const binary = this.build(source); + const kvk = new KVKFile(); + const file: Uint8Array = new Uint8Array(kvk.KVK_FILE.size(binary)); + const data = kvk.KVK_FILE.toBuffer(binary); + file.set(data, 0); + return file; + } + + private build(source: VisualKeyboard) { + const binary: BUILDER_KVK_FILE = { + header: { + identifier: BUILDER_KVK_HEADER_IDENTIFIER, + version: BUILDER_KVK_HEADER_VERSION, + associatedKeyboard: {len:0,str:''}, + flags: source.header.flags, + ansiFont:{ + color: source.header.ansiFont.color, + size: source.header.ansiFont.size, + name: {len:0,str:''} + }, + unicodeFont:{ + color: source.header.unicodeFont.color, + size: source.header.unicodeFont.size, + name: {len:0,str:''} + }, + }, + keyCount: source.keys.length, + keys:[] + }; + + this.setString(binary.header.associatedKeyboard, source.header.associatedKeyboard); + this.setString(binary.header.ansiFont.name, source.header.ansiFont.name); + this.setString(binary.header.unicodeFont.name, source.header.unicodeFont.name); + + for(let sourceKey of source.keys) { + const binaryKey: BUILDER_KVK_KEY = { + flags: sourceKey.flags, + vkey: sourceKey.vkey, + shift: sourceKey.shift, + text: { len: 0, str: '' }, + bitmap: 0 + }; + this.setString(binaryKey.text, sourceKey.text || ''); + binary.keys.push(binaryKey); + } + + return binary; + } + + /** + * Fills a kvk string from a source string. Note that the format includes both + * a length word and zero termination. + * + * @param str + * @param value + * @returns number + */ + private setString(str: BUILDER_KVK_STRING, value: string): void { + str.len = value.length + 1; + str.str = value; + } +}; diff --git a/common/web/types/src/kvk/kvk-file.ts b/common/web/types/src/kvk/kvk-file.ts new file mode 100644 index 00000000000..b33117e0f0f --- /dev/null +++ b/common/web/types/src/kvk/kvk-file.ts @@ -0,0 +1,122 @@ +import * as r from 'restructure'; +// +// Binary backing structures for .kvk format +// matching VisualKeyboardSaverBinary.pas +// + +export const BUILDER_KVK_HEADER_IDENTIFIER = 0x464B564B; // 'KVKF', little-endian +export const BUILDER_KVK_HEADER_VERSION = 0x0600; // Keyman 6.0 + +export interface BUILDER_KVK_STRING { + len: number; + str: string; +}; + +export interface BUILDER_KVK_FONT { + name: BUILDER_KVK_STRING; // 0000, variable length + size: number; // 4 bytes + color: number; // 4 bytes +}; + +export const BUILDER_KVK_FONT_Size = 8; // size of fixed elements of BUILDER_KVK_FONT + +export const enum BUILDER_KVK_KEY_FLAGS { + kvkkBitmap = 0x01, + kvkkUnicode = 0x02 +}; + +export const enum BUILDER_KVK_SHIFT_STATE { + KVKS_NORMAL = 0, + KVKS_SHIFT = 1, + KVKS_CTRL = 2, + KVKS_ALT = 4, + KVKS_LCTRL = 8, + KVKS_RCTRL = 16, + KVKS_LALT = 32, + KVKS_RALT = 64 +}; + +export interface BUILDER_KVK_KEY { + flags: BUILDER_KVK_KEY_FLAGS; // 0000, 1 byte + shift: number; // 0001, 2 bytes + vkey: number; // 0003, 2 bytes + text: BUILDER_KVK_STRING; // 0005, variable length + bitmap: number; // always 0 // 4 bytes +}; + +export const BUILDER_KVK_KEY_Size = 9; // size of fixed elements of BUILDER_KVK_KEY + +export const enum BUILDER_KVK_HEADER_FLAGS { + kvkh102 = 0x01, + kvkhDisplayUnderlying = 0x02, + kvkhUseUnderlying = 0x04, + kvkhAltGr = 0x08 +}; + +export interface BUILDER_KVK_HEADER { + identifier: number; // 0000, 4 bytes + version: number; // 0004, 4 bytes + flags: BUILDER_KVK_HEADER_FLAGS; // 0008, 1 byte + associatedKeyboard: BUILDER_KVK_STRING; // 0009, variable length + ansiFont: BUILDER_KVK_FONT; // variable length + unicodeFont: BUILDER_KVK_FONT; // variable length +}; + +export const BUILDER_KVK_HEADER_Size = 9 + BUILDER_KVK_FONT_Size + BUILDER_KVK_FONT_Size; // size of fixed elements of BUILDER_KVK_HEADER + +export interface BUILDER_KVK_FILE { + header: BUILDER_KVK_HEADER; // variable length + keyCount: number; // 4 bytes + keys: BUILDER_KVK_KEY[]; // variable length +}; + +export default class KVKFile { + public KVK_HEADER: any; + public KVK_KEY: any; + public KVK_KEYS: any; + public KVK_FONT: any; + public KVK_FILE: any; + public KVK_STRING: any; + + constructor() { + // + // Binary restructure definitions matching VisualKeyboardSaverBinary.pas + // TODO: move binaries to separate kvk-file.ts + // + + this.KVK_STRING = new r.Struct({ + len: r.int16le, + str: new r.String(null, 'utf16le') + }); + + this.KVK_FONT = new r.Struct({ + name: this.KVK_STRING, + size: r.int32le, + color: r.uint32le + }); + + this.KVK_KEY = new r.Struct({ + flags: r.uint8, + shift: r.uint16le, + vkey: r.uint16le, + text: this.KVK_STRING, + bitmap: r.uint32le // always 00 00 00 00 for now + }); + + this.KVK_HEADER = new r.Struct({ + identifier: r.uint32le, // KVKF + version: r.uint32le, // 0x0600 + flags: r.uint8, + associatedKeyboard: this.KVK_STRING, + ansiFont: this.KVK_FONT, + unicodeFont: this.KVK_FONT + }); + + this.KVK_FILE = new r.Struct({ + header: this.KVK_HEADER, + keyCount: r.uint32le, + keys: new r.Array(this.KVK_KEY, 'keyCount') + }); + + } +}; \ No newline at end of file diff --git a/common/web/types/src/kvk/kvks-file-reader.ts b/common/web/types/src/kvk/kvks-file-reader.ts new file mode 100644 index 00000000000..c6a696cf300 --- /dev/null +++ b/common/web/types/src/kvk/kvks-file-reader.ts @@ -0,0 +1,163 @@ +import * as xml2js from 'xml2js'; +import KVKSourceFile from './kvks-file.js'; +import Ajv from 'ajv'; +import { boxXmlArray } from '../util/util.js'; +import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKey, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js'; +import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js'; +import { BUILDER_KVK_HEADER_VERSION } from './kvk-file.js'; + +export enum KVKSParseErrorType { invalidVkey }; +export class KVKSParseError extends Error { + public type: KVKSParseErrorType; + public vkey: string; +}; + +export default class KVKSFileReader { + public read(file: Uint8Array): KVKSourceFile { + let source: KVKSourceFile; + + const parser = new xml2js.Parser({ + explicitArray: false, + mergeAttrs: false, + includeWhiteChars: true, + normalize: false, + emptyTag: {} as any + // Why "as any"? xml2js is broken: + // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means + // that an old version of `emptyTag` is used which doesn't support + // functions, but DefinitelyTyped is requiring use of function or a + // string. See also notes at + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470 + // An alternative fix would be to pull xml2js directly from github + // rather than using the version tagged on npmjs.com. + }); + + parser.parseString(file, (e: unknown, r: unknown) => { source = r as KVKSourceFile }); + source = this.boxArrays(source); + this.cleanupUnderscore('visualkeyboard', source.visualkeyboard); + return source; + } + + /** + * The only element that allows spaces is . Remove + * all other empty whitespace-only values. + * @param root + * @param source + */ + private cleanupUnderscore(root: string, source: any) { + if(root != 'key') { + if(source?.['_']?.trim() === '') { + delete source['_']; + } + } + + for(let key of Object.keys(source)) { + if(Array.isArray(source[key])) { + for(let item of source[key]) { + if(typeof(item) === 'object') { + this.cleanupUnderscore(key, item); + } + } + } else if(typeof source[key] === 'object') { + this.cleanupUnderscore(key, source[key]); + } + } + } + + public validate(source: KVKSourceFile, schemaBuffer: Buffer): void { + const schema = JSON.parse(schemaBuffer.toString('utf8')); + const ajv = new Ajv(); + if(!ajv.validate(schema, source)) { + throw new Error(ajv.errorsText()); + } + } + + public transform(source: KVKSourceFile, errors?: KVKSParseError[]): VisualKeyboard { + // NOTE: at this point, the xml should have been validated + // and matched the schema result so we can assume properties exist + let result: VisualKeyboard = { + header: { + version: BUILDER_KVK_HEADER_VERSION, + flags: 0, + ansiFont: { name: "Arial", size: -12, color: 0xFF000008 }, // TODO-LDML: consider defaults + unicodeFont: { name: "Arial", size: -12, color: 0xFF000008 }, // TODO-LDML: consider defaults + associatedKeyboard: source.visualkeyboard?.header?.kbdname, + underlyingLayout: source.visualkeyboard?.header?.layout, + }, + keys: [] + }; + + if(source.visualkeyboard?.header?.flags?.displayunderlying !== undefined) { + result.header.flags |= VisualKeyboardHeaderFlags.kvkhDisplayUnderlying; + } + if(source.visualkeyboard?.header?.flags?.key102 !== undefined) { + result.header.flags |= VisualKeyboardHeaderFlags.kvkh102; + } + if(source.visualkeyboard?.header?.flags?.usealtgr !== undefined) { + result.header.flags |= VisualKeyboardHeaderFlags.kvkhAltGr; + } + if(source.visualkeyboard?.header?.flags?.useunderlying !== undefined) { + result.header.flags |= VisualKeyboardHeaderFlags.kvkhUseUnderlying; + } + + for(let encoding of source.visualkeyboard.encoding) { + let isUnicode = (encoding.$?.name == 'unicode'), + font = isUnicode ? result.header.unicodeFont : result.header.ansiFont; + font.name = encoding.$?.fontname; + font.size = parseInt(encoding.$?.fontsize,10); + for(let layer of encoding.layer) { + let shift = this.kvksShiftToKvkShift(layer.$?.shift); + for(let sourceKey of layer.key) { + let vkey = (USVirtualKeyCodes as any)[sourceKey.$?.vkey]; + if(!vkey) { + if(errors) { + let e = new KVKSParseError(); + e.type = KVKSParseErrorType.invalidVkey; + e.vkey = sourceKey.$?.vkey; + errors.push(e); + } + continue; + } + let key: VisualKeyboardKey = { + flags: isUnicode ? VisualKeyboardKeyFlags.kvkkUnicode : 0, // TODO-LDML: bitmap support + shift: shift, + text: sourceKey._ ?? '', + vkey: vkey + } + result.keys.push(key); + } + } + } + + return result; + } + + /** + * xml2js will not place single-entry objects into arrays. + * Easiest way to fix this is to box them ourselves as needed + * @param source KVKSourceFile + */ + private boxArrays(source: KVKSourceFile) { + boxXmlArray(source.visualkeyboard, 'encoding'); + for(let encoding of source.visualkeyboard.encoding) { + boxXmlArray(encoding, 'layer'); + for(let layer of encoding.layer) { + boxXmlArray(layer, 'key'); + } + } + return source; + } + + + public kvksShiftToKvkShift(shift: string): VisualKeyboardShiftState { + shift = shift.toUpperCase(); + + // TODO-LDML(lowpri): make a map of this? + for(let state of VisualKeyboardLegalShiftStates) { + if(state.name == shift) { + return state.shift; + } + } + return 0; + } +} \ No newline at end of file diff --git a/common/web/types/src/kvk/kvks-file-writer.ts b/common/web/types/src/kvk/kvks-file-writer.ts new file mode 100644 index 00000000000..613c6caa510 --- /dev/null +++ b/common/web/types/src/kvk/kvks-file-writer.ts @@ -0,0 +1,113 @@ +import * as xml2js from 'xml2js'; +import KVKSourceFile, { KVKSEncoding, KVKSFlags, KVKSKey, KVKSLayer } from './kvks-file.js'; +import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKeyFlags, VisualKeyboardLegalShiftStates, VisualKeyboardShiftState } from './visual-keyboard.js'; +import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js'; + +export default class KVKSFileWriter { + public write(vk: VisualKeyboard): string { + + const builder = new xml2js.Builder({ + allowSurrogateChars: true, + attrkey: '$', + charkey: '_', + xmldec: { + version: '1.0', + encoding: 'UTF-8', + standalone: true + } + }) + + let flags: KVKSFlags = {}; + if(vk.header.flags & VisualKeyboardHeaderFlags.kvkhDisplayUnderlying) { + flags.displayunderlying = ''; + } + if(vk.header.flags & VisualKeyboardHeaderFlags.kvkh102) { + flags.key102 = ''; + } + if(vk.header.flags & VisualKeyboardHeaderFlags.kvkhAltGr) { + flags.usealtgr = ''; + } + if(vk.header.flags & VisualKeyboardHeaderFlags.kvkhUseUnderlying) { + flags.useunderlying = ''; + } + + + + let kvks: KVKSourceFile = { + visualkeyboard: { + header: { + version: '10.0', + kbdname: vk.header.associatedKeyboard, + flags: flags, + }, + encoding: [] + } + }; + + if(vk.header.underlyingLayout) kvks.visualkeyboard.header.layout = vk.header.underlyingLayout; + + let encodings: {ansi: {o: KVKSEncoding, l: {[name:string]:KVKSLayer}}, unicode: {o: KVKSEncoding, l: {[name:string]:KVKSLayer}}} = {ansi:null,unicode:null}; + + for(let key of vk.keys) { + const encoding = key.flags & VisualKeyboardKeyFlags.kvkkUnicode ? 'unicode' : 'ansi'; + const shift = this.kvkShiftToKvksShift(key.shift); + + if(!encodings[encoding]) { + encodings[encoding] = { + o: { + layer: [], + $: {name: encoding, + fontname: encoding == 'ansi' ? vk.header.ansiFont.name : vk.header.unicodeFont.name, + fontsize: (encoding == 'ansi' ? vk.header.ansiFont.size : vk.header.unicodeFont.size).toString(), + }, + }, + l: {} + }; + kvks.visualkeyboard.encoding.push(encodings[encoding].o); + } + let e = encodings[encoding]; + if(!e.l[shift]) { + e.l[shift] = { + key: [], + $: {shift: shift}, + }; + e.o.layer.push(e.l[shift]); + } + let l = e.l[shift]; + + // TODO-LDML: map + let vkeyName = ''; + for(let vkey of Object.keys(USVirtualKeyCodes)) { + if((USVirtualKeyCodes as any)[vkey] == key.vkey) { + vkeyName = vkey; + break; + } + } + + if(vkeyName == '') { + //TODO-LDML: warn + continue; + } + let k: KVKSKey = { + $: {vkey: vkeyName}, + _: key.text, + } + + l.key.push(k); + } + + // console.dir(kvks, {depth:8}); + let result = builder.buildObject(kvks); + return result; //Uint8Array.from(result); + } + + public kvkShiftToKvksShift(shift: VisualKeyboardShiftState): string { + // TODO-LDML(lowpri): make a map of this? + for(let state of VisualKeyboardLegalShiftStates) { + if(state.shift == shift) { + return state.name; + } + } + return ''; + } +} \ No newline at end of file diff --git a/common/web/types/src/kvk/kvks-file.ts b/common/web/types/src/kvk/kvks-file.ts new file mode 100644 index 00000000000..d3884d9a492 --- /dev/null +++ b/common/web/types/src/kvk/kvks-file.ts @@ -0,0 +1,53 @@ +// .kvks xml format +// See VisualKeyboardLoaderXML.pas, not the same as VisualKeyboardImportXML.pas! + +export default interface KVKSourceFile { + /** + * -- the root element. + */ + visualkeyboard: KVKSVisualKeyboard; +} + +export interface KVKSVisualKeyboard { + header?: KVKSHeader; + encoding?: KVKSEncoding[]; +}; + +export interface KVKSHeader { + version?: string; + kbdname?: string; + flags?: KVKSFlags; + layout?: string; +}; + +export interface KVKSFlags { + key102?: string; + displayunderlying?: string; + useunderlying?: string; + usealtgr?: string; +}; + +export interface KVKSEncoding { + $?: { + name?: string; + fontname?: string; + fontsize?: string; + }; + layer?: KVKSLayer[]; +}; + +export interface KVKSLayer { + $?: { + shift?: string; + }; + key?: KVKSKey[]; +}; + +export interface KVKSKey { + $?: { + vkey?: string; + } + bitmap?: string; + _?: string; +}; + diff --git a/common/web/types/src/kvk/visual-keyboard.ts b/common/web/types/src/kvk/visual-keyboard.ts new file mode 100644 index 00000000000..11e9df977da --- /dev/null +++ b/common/web/types/src/kvk/visual-keyboard.ts @@ -0,0 +1,80 @@ +// +// Visual Keyboard in-memory data +// +// Corresponds to .kvk / .kvks file data +// + +import { BUILDER_KVK_SHIFT_STATE, BUILDER_KVK_HEADER_FLAGS, BUILDER_KVK_KEY_FLAGS } from "./kvk-file"; + +export class VisualKeyboard { + header: VisualKeyboardHeader = {flags: 0, ansiFont:{}, unicodeFont:{}, underlyingLayout: undefined}; + keys: VisualKeyboardKey[] = []; +}; + +export { BUILDER_KVK_HEADER_FLAGS as VisualKeyboardHeaderFlags } from "./kvk-file"; + +export class VisualKeyboardHeader { + version?: number; // 0x0600 + flags: BUILDER_KVK_HEADER_FLAGS; + associatedKeyboard?: string; + ansiFont: VisualKeyboardFont; // generally unused + unicodeFont: VisualKeyboardFont; + underlyingLayout?: string; +}; + +export class VisualKeyboardFont { + name?: string; + size?: number; + color?: number; // unused +}; + +export { BUILDER_KVK_KEY_FLAGS as VisualKeyboardKeyFlags } from "./kvk-file.js"; + +export { BUILDER_KVK_SHIFT_STATE as VisualKeyboardShiftState } from "./kvk-file.js"; + +export class VisualKeyboardKey { + flags?: BUILDER_KVK_KEY_FLAGS; + shift?: BUILDER_KVK_SHIFT_STATE; + vkey?: number; + text?: string; + //bitmap: bitmap-image, unsupported in ldml keyboards; may include for round-tripabilty? +}; + +interface VisualKeyboardLegalShiftState { + desc: string; + name: string; + shift: number; + vkeys: number[]; +}; + +import { BUILDER_KVK_SHIFT_STATE as ss } from "./kvk-file.js"; +import { USVirtualKeyCodes as vk } from "../consts/virtual-key-constants.js"; + +export const VisualKeyboardLegalShiftStates: VisualKeyboardLegalShiftState[] = [ + {desc: 'Unshifted', name: '', shift: ss.KVKS_NORMAL, vkeys: []}, //1 + + {desc: 'Shift', name: 'S', shift: ss.KVKS_SHIFT, vkeys: [vk.K_SHIFT]}, + {desc: 'Ctrl', name: 'C', shift: ss.KVKS_CTRL, vkeys: [vk.K_CONTROL]}, + {desc: 'Alt', name: 'A', shift: ss.KVKS_ALT, vkeys: [vk.K_ALT]}, + {desc: 'Shift+Ctrl', name: 'SC', shift: ss.KVKS_SHIFT | ss.KVKS_CTRL, vkeys: [vk.K_SHIFT,vk.K_CONTROL]}, + {desc: 'Shift+Alt', name: 'SA', shift: ss.KVKS_SHIFT | ss.KVKS_ALT, vkeys: [vk.K_SHIFT,vk.K_ALT]}, + {desc: 'Ctrl+Alt', name: 'CA', shift: ss.KVKS_CTRL | ss.KVKS_ALT, vkeys: [vk.K_CONTROL,vk.K_ALT]}, + {desc: 'Shift+Ctrl+Alt', name: 'SCA', shift: ss.KVKS_SHIFT | ss.KVKS_CTRL | ss.KVKS_ALT, vkeys: [vk.K_SHIFT,vk.K_CONTROL,vk.K_ALT]}, //7 + + {desc: 'Left Ctrl', name: 'LC', shift: ss.KVKS_LCTRL, vkeys: [vk.K_LCONTROL]}, + {desc: 'Right Ctrl', name: 'RC', shift: ss.KVKS_RCTRL, vkeys: [vk.K_RCONTROL]}, + {desc: 'Left Alt', name: 'LA', shift: ss.KVKS_LALT, vkeys: [vk.K_LALT]}, + {desc: 'Right Alt', name: 'RA', shift: ss.KVKS_RALT, vkeys: [vk.K_RALT]}, + {desc: 'Shift+Left Ctrl', name: 'SLC', shift: ss.KVKS_SHIFT | ss.KVKS_LCTRL, vkeys: [vk.K_SHIFT,vk.K_LCONTROL]}, + {desc: 'Shift+Right Ctrl', name: 'SRC', shift: ss.KVKS_SHIFT | ss.KVKS_RCTRL, vkeys: [vk.K_SHIFT,vk.K_RCONTROL]}, + {desc: 'Shift+Left Alt', name: 'SLA', shift: ss.KVKS_SHIFT | ss.KVKS_LALT, vkeys: [vk.K_SHIFT,vk.K_LALT]}, + {desc: 'Shift+Right Alt', name: 'SRA', shift: ss.KVKS_SHIFT | ss.KVKS_RALT, vkeys: [vk.K_SHIFT,vk.K_RALT]}, + {desc: 'Left Ctrl+Left Alt', name: 'LCLA', shift: ss.KVKS_LCTRL | ss.KVKS_LALT, vkeys: [vk.K_LCONTROL,vk.K_LALT]}, + {desc: 'Left Ctrl+Right Alt', name: 'LCRA', shift: ss.KVKS_LCTRL | ss.KVKS_RALT, vkeys: [vk.K_LCONTROL,vk.K_RALT]}, + {desc: 'Right Ctrl+Left Alt', name: 'RCLA', shift: ss.KVKS_RCTRL | ss.KVKS_LALT, vkeys: [vk.K_RCONTROL,vk.K_LALT]}, + {desc: 'Right Ctrl+Right Alt', name: 'RCRA', shift: ss.KVKS_RCTRL | ss.KVKS_RALT, vkeys: [vk.K_RCONTROL,vk.K_RALT]}, + {desc: 'Shift+Left Ctrl+Left Alt', name: 'SLCLA', shift: ss.KVKS_SHIFT | ss.KVKS_LCTRL | ss.KVKS_LALT, vkeys: [vk.K_SHIFT, vk.K_LCONTROL,vk.K_LALT]}, + {desc: 'Shift+Left Ctrl+Right Alt', name: 'SLCRA', shift: ss.KVKS_SHIFT | ss.KVKS_LCTRL | ss.KVKS_RALT, vkeys: [vk.K_SHIFT, vk.K_LCONTROL,vk.K_RALT]}, + {desc: 'Shift+Right Ctrl+Left Alt', name: 'SRCLA', shift: ss.KVKS_SHIFT | ss.KVKS_RCTRL | ss.KVKS_LALT, vkeys: [vk.K_SHIFT, vk.K_RCONTROL,vk.K_LALT]}, + {desc: 'Shift+Right Ctrl+Right Alt', name: 'SRCRA', shift: ss.KVKS_SHIFT | ss.KVKS_RCTRL | ss.KVKS_RALT, vkeys: [vk.K_SHIFT, vk.K_RCONTROL,vk.K_RALT]}, //16 +]; diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts new file mode 100644 index 00000000000..c0a5e9dd6d7 --- /dev/null +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-testdata-xml.ts @@ -0,0 +1,74 @@ +// +// Conforms to techpreview +// +// The interfaces in this file are designed with reference to the mapped +// structures produced by xml2js when passed a LDML keyboard test data .xml file. +// +// Using prefix LKT for LDML Keyboard Test +// + +export interface LDMLKeyboardTestDataXMLSourceFile { + /** + * -- the root element. + */ + keyboardTest: LKTKeyboardTest; +} + +export interface LKTKeyboardTest { + conformsTo?: string; + info?: LKTInfo; + repertoire?: LKTRepertoire[]; + tests?: LKTTests[]; +}; + +export interface LKTInfo { + author?: string; + keyboard?: string; + name?: string; +}; + +export interface LKTRepertoire { + name?: string; + chars?: string; + type?: string; +}; + +export interface LKTTests { + name?: string; + test?: LKTTest[]; +}; + +export interface LKTTest { + name?: string; + startContext?: LKTStartContext; + actions?: LKTAction[]; // differs from XML, to represent order of actions +}; + +export interface LKTStartContext { + to?: string; +}; + +export interface LKTCheck { + result?: string; +}; + +export interface LKTEmit { + to?: string; +}; + +export interface LKTKeystroke { + key?: string; + flick?: string; + longPress?: string; + tapCount?: string; +}; + +/** + * Test Actions. + * The expectation is that each LKTAction object will have exactly one non-falsy field. + */ +export interface LKTAction { + check?: LKTCheck; + emit?: LKTEmit; + keystroke?: LKTKeystroke; +}; 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 new file mode 100644 index 00000000000..7172edbd700 --- /dev/null +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -0,0 +1,386 @@ +import * as xml2js from 'xml2js'; +import { LDMLKeyboardXMLSourceFile, LKImport } from './ldml-keyboard-xml.js'; +import Ajv from 'ajv'; +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'; + +interface NameAndProps { + '$'?: any; // content + '#name'?: string; // element name + '$$'?: any; // children +}; + +export default class LDMLKeyboardXMLSourceFileReader { + callbacks: CompilerCallbacks; + + constructor(callbacks : CompilerCallbacks) { + this.callbacks = callbacks; + } + + readImportFile(version: string, subpath: string): Buffer { + // TODO-LDML: sanitize input string + let importPath = new URL(`../import/${version}/${subpath}`, import.meta.url); + // TODO-LDML: support baseFileName? + return this.callbacks.loadFile(importPath.pathname, importPath); + } + + /** + * xml2js will not place single-entry objects into arrays. + * Easiest way to fix this is to box them ourselves as needed + * @param source any + * @returns true on success, false on failure + */ + private boxArrays(source: any) : boolean { + if (source?.keyboard) { + if (!source.keyboard.keys) { + source.keyboard.keys = { + key: [], + flicks: [], + }; + } + if (!source.keyboard.keys.import) { + source.keyboard.keys.import = []; + } + } + boxXmlArray(source?.keyboard, 'layers'); + boxXmlArray(source?.keyboard?.displays, 'display'); + boxXmlArray(source?.keyboard?.names, 'name'); + boxXmlArray(source?.keyboard?.vkeys, 'vkey'); + boxXmlArray(source?.keyboard?.keys, 'key'); + boxXmlArray(source?.keyboard?.keys, 'flicks'); + boxXmlArray(source?.keyboard?.locales, 'locale'); + boxXmlArray(source?.keyboard, 'transforms'); + if(source?.keyboard?.layers) { + for(let layers of source?.keyboard?.layers) { + boxXmlArray(layers, 'layer'); + if(layers?.layer) { + for(let layer of layers?.layer) { + boxXmlArray(layer, 'row'); + } + } + } + } + if(source?.keyboard?.keys?.flicks) { + for(let flicks of source?.keyboard?.keys?.flicks) { + boxXmlArray(flicks, 'flick'); + } + } + if(source?.keyboard?.transforms) { + for(let transform of source.keyboard.transforms) { + boxXmlArray(transform, 'transform'); + } + } + boxXmlArray(source?.keyboard?.reorders, 'reorder'); + return this.boxImportsAndSpecials(source, 'keyboard'); + } + + /** + * Recurse over object, boxing up any specials or imports + * @param obj any object to be traversed + * @param subtag the leafmost enclosing tag such as 'keyboard' + * @returns true on success, false on failure + */ + private boxImportsAndSpecials(obj: any, subtag: string) : boolean { + if (!obj) return true; + if (Array.isArray(obj)) { + for (const sub of obj) { + // retain the same subtag + if (!this.boxImportsAndSpecials(sub, subtag)) { + return false; + } + } + } else if(typeof obj === 'object') { + for (const key of Object.keys(obj)) { + if (key === 'special') { + boxXmlArray(obj, key); + } else if(key === 'import') { + // Need to 'box it up' first for processing + boxXmlArray(obj, key); + // Now, resolve the import + if (!this.resolveImports(obj, subtag)) { + return false; + } + // now delete the import array we so carefully constructed, the caller does not + // want to see it. + delete obj['import']; + } else { + if (!this.boxImportsAndSpecials(obj[key], key)) { + return false; + } + } + } + } + return true; + } + + /** + * + * @param obj object to be imported into + * @param subtag obj's element tag, e.g. `keys` + * @returns true on success, false on failure + */ + private resolveImports(obj: any, subtag: string) : boolean { + // These are in reverse order, because the imports insert at the beginning of the array. + // first, the explicit imports + for (const asImport of ([...obj['import'] as LKImport[]].reverse())) { + if (!this.resolveOneImport(obj, subtag, asImport)) { + return false; + } + } + // then, the implied imports + if (subtag === 'keys') { + // + if (!this.resolveOneImport(obj, subtag, { + base: constants.cldr_import_base, + path: constants.cldr_implied_keys_import + })) { + return false; + } + } + return true; + } + + /** + * @param obj the object being imported into + * @param subtag obj's element tag, e.g. `keys` + * @param asImport the import structure + * @returns true on success, false on failure + */ + private resolveOneImport(obj: any, subtag: string, asImport: LKImport) : boolean { + const { base, path } = asImport; + if (base !== constants.cldr_import_base) { + this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidBase({base, path, subtag})); + return false; + } + const paths = path.split('/'); + if (paths[0] == '' || paths[1] == '' || paths.length !== 2) { + this.callbacks.reportMessage(CommonTypesMessages.Error_ImportInvalidPath({base, path, subtag})); + return false; + } + const importData: Uint8Array = this.readImportFile(paths[0], paths[1]); + if (!importData || !importData.length) { + this.callbacks.reportMessage(CommonTypesMessages.Error_ImportReadFail({base, path, subtag})); + return false; + } + const importXml: any = this.loadUnboxed(importData); // TODO-LDML: have to load as any because it is an arbitrary part + const importRootNode = importXml[subtag]; // e.g. + + // importXml will have one property: the root element. + if (!importRootNode) { + this.callbacks.reportMessage(CommonTypesMessages.Error_ImportWrongRoot({base, path, subtag})); + return false; + } + // pull all children of importXml[subtag] into obj + for (const subsubtag of Object.keys(importRootNode).reverse()) { // e.g. + const subsubval = importRootNode[subsubtag]; + 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; + } + if (!obj[subsubtag]) { + obj[subsubtag] = []; // start with empty array + } + obj[subsubtag] = [...subsubval, ...obj[subsubtag]]; + } + return true; + } + + /** + * @returns true if valid, false if invalid + */ + public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile, schemaSource: Buffer): boolean { + const schema = JSON.parse(schemaSource.toString('utf8')); + const ajv = new Ajv(); + if(!ajv.validate(schema, source)) { + for (let err of ajv.errors) { + this.callbacks.reportMessage(CommonTypesMessages.Error_SchemaValidationError({ + instancePath: err.instancePath, + keyword: err.keyword, + message: err.message || 'Unknown AJV Error', // docs say 'message' is optional if 'messages:false' in options + params: Object.entries(err.params || {}).sort().map(([k,v])=>`${k}="${v}"`).join(' '), + })); + } + return false; + } + return true; + } + + loadUnboxed(file: Uint8Array): LDMLKeyboardXMLSourceFile { + let source = (() => { + let a: LDMLKeyboardXMLSourceFile; + let parser = new xml2js.Parser({ + explicitArray: false, + mergeAttrs: true, + includeWhiteChars: false, + emptyTag: {} as any + // Why "as any"? xml2js is broken: + // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means + // that an old version of `emptyTag` is used which doesn't support + // functions, but DefinitelyTyped is requiring use of function or a + // string. See also notes at + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470 + // An alternative fix would be to pull xml2js directly from github + // rather than using the version tagged on npmjs.com. + }); + parser.parseString(file, (e: unknown, r: unknown) => { a = r as LDMLKeyboardXMLSourceFile }); // TODO-LDML: isn't 'e' the error? + return a; + })(); + return source; + } + + /** + * @param file + * @returns source on success, otherwise null + */ + public load(file: Uint8Array): LDMLKeyboardXMLSourceFile | null { + if (!file) { + return null; + } + const source = this.loadUnboxed(file); + if(this.boxArrays(source)) { + return source; + } else { + return null; + } + } + + loadTestDataUnboxed(file: Uint8Array): any { + let source = (() => { + let a: any; + let parser = new xml2js.Parser({ + // explicitArray: false, + preserveChildrenOrder:true, // needed for test data + explicitChildren: true, // needed for test data + // mergeAttrs: true, + // includeWhiteChars: false, + // emptyTag: {} as any + // Why "as any"? xml2js is broken: + // https://github.com/Leonidas-from-XIV/node-xml2js/issues/648 means + // that an old version of `emptyTag` is used which doesn't support + // functions, but DefinitelyTyped is requiring use of function or a + // string. See also notes at + // https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59259#issuecomment-1254405470 + // An alternative fix would be to pull xml2js directly from github + // rather than using the version tagged on npmjs.com. + }); + parser.parseString(file, (e: unknown, r: unknown) => { a = r as any }); // TODO-LDML: isn't 'e' the error? + return a; // Why 'any'? Because we need to box up the $'s into proper properties. + })(); + return source; + } + + /** + * Filter the obj array for a subtag + * @param source array of source objs + * @param subtag subtag to filter on + * @returns + */ + findSubtagArray(source: NameAndProps[], subtag: string): NameAndProps[] { + return source?.filter(o => o['#name'] === subtag); + } + + /** + * Get exactly one element + * @param source + * @param subtag + * @returns + */ + findSubtag(source: NameAndProps[], subtag: string): NameAndProps | null { + const r = this.findSubtagArray(source, subtag); + if (!r || r.length === 0) { + return null; + } else if (r.length === 1) { + return r[0]; + } else { + this.callbacks.reportMessage(CommonTypesMessages.Error_TestDataUnexpectedArray({subtag})); + return null; // ERROR + } + } + + /** + * The default test data stuffer. + * Just gets $ (the attrs) as the body. + * Override to use something more complex, such as including child nodes. + * @param o object to map + * @param r back ref to reader + */ + static readonly defaultMapper = ((o : NameAndProps, r: LDMLKeyboardXMLSourceFileReader) => o?.$); + + /** + * + * @param obj target object + * @param source array of $/#name strings + * @param subtag name to extract + * @param mapper custom mapper function + */ + stuffBoxes(obj: any, source: NameAndProps[], subtag: string, asArray?: boolean, mapper?: (v: NameAndProps, r: LDMLKeyboardXMLSourceFileReader) => any) { + if (!mapper) { + mapper = LDMLKeyboardXMLSourceFileReader.defaultMapper; + } + if (asArray) { + const r = this; + obj[subtag] = this.findSubtagArray(source, subtag)?.map((v) => mapper(v, r)); // extract contents only + } else { + obj[subtag] = mapper(this.findSubtag(source, subtag), this); // run the mapper once + } + } + + boxTestDataArrays(raw: any) : LDMLKeyboardTestDataXMLSourceFile | null { + if (!raw) return null; + const a : LDMLKeyboardTestDataXMLSourceFile = { + keyboardTest: { + conformsTo: raw?.keyboardTest?.$?.conformsTo, + } + }; + + const $$ : NameAndProps[] = raw?.keyboardTest?.$$; + + this.stuffBoxes(a.keyboardTest, $$, 'info'); + this.stuffBoxes(a.keyboardTest, $$, 'repertoire', true); + this.stuffBoxes(a.keyboardTest, $$, 'tests', true, (o, r) => { + // start with basic unpack + const tests : LKTTests = LDMLKeyboardXMLSourceFileReader.defaultMapper(o, r); + // add ingredients + r.stuffBoxes(tests, o.$$, 'test', true, (o, r) => { + // start with basic unpack + const test : LKTTest = LDMLKeyboardXMLSourceFileReader.defaultMapper(o, r); + // add ingredients + const $$ : NameAndProps[] = o.$$; + r.stuffBoxes(test, $$, 'startContext'); // singleton + // now the actions + test.actions = $$.map(v => { + const subtag = v['#name']; + const subv = LDMLKeyboardXMLSourceFileReader.defaultMapper(v, r); + switch(subtag) { + case 'keystroke': return { keystroke: subv }; + case 'check': return { check: subv }; + case 'emit': return { emit: subv }; + case 'startContext': return null; // handled above + default: this.callbacks.reportMessage(CommonTypesMessages.Error_TestDataUnexpectedAction({ subtag })); return null; + } + }).filter(v => v !== null); + return test; + }); + return tests; + }); + + return a; + } + + /** + * @param file test file + * @returns source on success, otherwise null + */ + public loadTestData(file: Uint8Array): LDMLKeyboardTestDataXMLSourceFile | null { + if (!file) { + return null; + } + const source = this.loadTestDataUnboxed(file); + return this.boxTestDataArrays(source); + } +} diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts new file mode 100644 index 00000000000..6d0d3e09802 --- /dev/null +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts @@ -0,0 +1,181 @@ +// +// Conforms to techpreview +// +// The interfaces in this file are designed with reference to the mapped +// structures produced by xml2js when passed a LDML keyboard .xml file. +// +// Using prefix LK for LDML Keyboard +// + +export interface LDMLKeyboardXMLSourceFile { + /** + * -- the root element. + */ + keyboard: LKKeyboard; +} + +export interface LKKeyboard { + locale?: string; + conformsTo?: string; + + locales?: LKLocales; + version?: LKVersion; + info?: LKInfo; + names?: LKNames; + settings?: LKSettings; + keys?: LKKeys; + displays?: LKDisplays; + layers?: LKLayers[]; + vkeys?: LKVkeys; + transforms?: LKTransforms[]; + reorders?: LKReorders; +}; + +/** + * This is defined as an interface, but actually is resolved during the reading phase + */ +export interface LKImport { + /** + * import base, currently `cldr` is supported + */ + base: string; + /** + * path to imported resource, of the form `techpreview/*.xml` + */ + path: string; +}; + +export interface LKLocales { + locale: LKLocale[]; +}; + +export interface LKLocale { + id?: string; +}; + +export interface LKVersion { + number: string; // semver string +} + +export interface LKInfo { + author?: string; + indicator?: string; + layout?: string; + normalization?: string; +}; + +export interface LKNames { + name: LKName[]; +}; + +export interface LKName { + value?: string; +}; + +export interface LKSettings { + fallback: "omit"; + transformFailure: "omit"; + transformPartial: "hide"; +}; + +export interface LKKeys { + key: LKKey[]; + flicks: LKFlicks[]; +}; + +export interface LKKey { + id?: string; + flicks?: string; + to?: string; + gap?: boolean; + switch?: string; + longPress?: string; + longPressDefault?: string; + multiTap?: string; + transform?: "no"; + width?: number; +}; + +export interface LKFlicks { + id?: string; + flick?: LKFlick[]; +}; + +export interface LKFlick { + directions?: string; + to?: string; +}; + +export interface LKLayers { + /** + * `hardware` or `touch` + */ + form?: string; + /** + * `us`, `iso`, `jis`, or `abnt2` + */ + hardware?: string; + /** + * Minimum width in millimeters + */ + minDeviceWidth?: number; + layer?: LKLayer[]; +}; + +export interface LKLayer { + id?: string; + modifier?: string; + row?: LKRow[]; +}; + +export interface LKRow { + keys?: string; +}; + +export interface LKVkeys { + vkey?: LKVkey[]; +}; + +export interface LKVkey { + from?: string; + to?: string; +}; + +export interface LKTransforms { + type?: "simple" | "final"; + transform: LKTransform[]; +}; + +export interface LKTransform { + from?: string; + to?: string; + before?: string; + error?: "fail"; +}; + +export interface LKReorders { + reorder: LKReorder[]; +}; + +export interface LKReorder { + from?: string; + before?: string; + order?: string; + tertiary?: string; + tertiary_base?: string; + prebase?: string; +}; + +export interface LKDisplayOptions { + baseCharacter?: string; +}; + +export interface LKDisplay { + to?: string; + display?: string; +}; + +export interface LKDisplays { + display?: LKDisplay[]; + displayOptions?: LKDisplayOptions; +}; diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts new file mode 100644 index 00000000000..72cf77c48e5 --- /dev/null +++ b/common/web/types/src/main.ts @@ -0,0 +1,27 @@ + +export * as KMX from './kmx/kmx.js'; +export * as KMXPlus from './kmx/kmx-plus.js'; +export { default as KMXBuilder } from './kmx/kmx-builder.js'; + +export * as VisualKeyboard from './kvk/visual-keyboard.js'; +export { default as KMXPlusBuilder} from './kmx/kmx-plus-builder/kmx-plus-builder.js'; +export { default as KvkFileReader } from './kvk/kvk-file-reader.js'; +export { default as KvksFileReader } from './kvk/kvks-file-reader.js'; +export { default as KvkFileWriter } from './kvk/kvk-file-writer.js'; + +export * as LDMLKeyboard from './ldml-keyboard/ldml-keyboard-xml.js'; +export { LDMLKeyboardTestDataXMLSourceFile } from './ldml-keyboard/ldml-keyboard-testdata-xml'; +export { default as LDMLKeyboardXMLSourceFileReader } from './ldml-keyboard/ldml-keyboard-xml-reader.js'; + +export * as Constants from './consts/virtual-key-constants.js'; + +export { CompilerCallbacks, CompilerEvent, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec } from './util/compiler-interfaces.js'; +export { CommonTypesMessages } from './util/common-events.js'; + +export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js'; +export { TouchLayoutFileReader } from './keyman-touch-layout/keyman-touch-layout-file-reader.js'; +export { TouchLayoutFileWriter, TouchLayoutFileWriterOptions } from './keyman-touch-layout/keyman-touch-layout-file-writer.js'; + +export * as KPJ from './kpj/kpj-file.js'; +export { KPJFileReader } from './kpj/kpj-file-reader.js'; +export { KeymanDeveloperProject } from './kpj/keyman-developer-project.js'; diff --git a/common/web/types/src/restructure.d.ts b/common/web/types/src/restructure.d.ts new file mode 100644 index 00000000000..9750d24473a --- /dev/null +++ b/common/web/types/src/restructure.d.ts @@ -0,0 +1,2 @@ +// TODO: consider defining more types? +declare module 'restructure'; \ No newline at end of file diff --git a/common/web/types/src/util/common-events.ts b/common/web/types/src/util/common-events.ts new file mode 100644 index 00000000000..b8b33005504 --- /dev/null +++ b/common/web/types/src/util/common-events.ts @@ -0,0 +1,50 @@ +import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from './compiler-interfaces.js'; +import { constants } from '@keymanapp/ldml-keyboard-constants'; + +const CommonTypesErrMask = CompilerErrorNamespace.CommonTypes; +// const SevInfo = CompilerErrorSeverity.Info | CommonTypesErrMask; +// const SevHint = CompilerErrorSeverity.Hint | CommonTypesErrMask; +// const SevWarn = CompilerErrorSeverity.Warn | CommonTypesErrMask; +const SevError = CompilerErrorSeverity.Error | CommonTypesErrMask; +// const SevFatal = CompilerErrorSeverity.Fatal | CommonTypesErrMask; + +export class CommonTypesMessages { + // structured Ajv validation error + static Error_SchemaValidationError = (o:{instancePath:string, keyword:string, message: string, params: string}) => m(this.ERROR_SchemaValidationError, + `Error validating LDML XML file: ${o.instancePath}: ${o.keyword}: ${o.message} ${o.params}`); + static ERROR_SchemaValidationError = SevError | 0x0001; + + static Error_ImportInvalidBase = (o: { base: string, path: string, subtag: string }) => + m(this.ERROR_ImportInvalidBase, + `Import element with base ${o.base} is unsupported. Only ${constants.cldr_import_base} is supported.`); + static ERROR_ImportInvalidBase = SevError | 0x0002; + + static Error_ImportInvalidPath = (o: { base: string, path: string, subtag: string }) => + m(this.ERROR_ImportInvalidPath, + `Import element with invalid path ${o.path}: expected the form '${constants.cldr_version_latest}./*.xml`); + static ERROR_ImportInvalidPath = SevError | 0x0003; + + static Error_ImportReadFail = (o: { base: string, path: string, subtag: string }) => + m(this.ERROR_ImportReadFail, + `Import could not read data with path ${o.path}: expected the form '${constants.cldr_version_latest}./*.xml'`); + static ERROR_ImportReadFail = SevError | 0x0004; + + static Error_ImportWrongRoot = (o: { base: string, path: string, subtag: string }) => + m(this.ERROR_ImportWrongRoot, + `Invalid import file ${o.path}: expected ${o.subtag} as root element.`); + static ERROR_ImportWrongRoot = SevError | 0x0005; + + static Error_ImportMergeFail = (o: { base: string, path: string, subtag: string, subsubtag: string }) => + m(this.ERROR_ImportMergeFail, + `Problem importing ${o.path}: not sure how to handle non-array ${o.subtag}.${o.subsubtag}`); + static ERROR_ImportMergeFail = SevError | 0x0006; + + static Error_TestDataUnexpectedArray = (o: {subtag: string}) => + m(this.ERROR_TestDataUnexpectedArray, + `Problem reading test data: expected single ${o.subtag} element, found multiple`); + static ERROR_TestDataUnexpectedArray = SevError | 0x0007; + static Error_TestDataUnexpectedAction = (o: {subtag: string}) => + m(this.ERROR_TestDataUnexpectedAction, + `Problem reading test data: unexpected action element ${o.subtag}`); + static ERROR_TestDataUnexpectedAction = SevError | 0x0008; +}; diff --git a/common/web/types/src/util/compiler-interfaces.ts b/common/web/types/src/util/compiler-interfaces.ts new file mode 100644 index 00000000000..8375e0ea43f --- /dev/null +++ b/common/web/types/src/util/compiler-interfaces.ts @@ -0,0 +1,54 @@ +/** + * Abstract interface for compiler error and warning messages + */ +export interface CompilerEvent { + code: number; + message: string; +}; + +export enum CompilerErrorSeverity { + Info = 0x000000, // Informational, not necessarily a problem + Hint = 0x100000, // Something the user might want to be aware of + Warn = 0x200000, // Warning: Not great, but we can keep going. + Error = 0x300000, // Severe error where we can't continue + Fatal = 0x400000, // OOM or should-not-happen internal problem + + Severity_Mask = 0xF00000, // includes reserved bits + Error_Mask = 0x0FFFFF, +}; + +export enum CompilerErrorNamespace { + /** + * kmc-keyboard errors between 0x0000…0x0FFF + */ + KeyboardCompiler = 0x0000, + /** + * common/web/types errors between 0x1000…0x1FFF + */ + CommonTypes = 0x1000, +}; + +/** + * Abstract interface for callbacks, to abstract out file i/o + */ +export interface CompilerCallbacks { + /** + * Attempt to load a file. Return falsy if not found. + * @param baseFilename + * @param filename + */ + loadFile(baseFilename: string, filename: string | URL): Buffer; + loadLdmlKeyboardSchema(): Buffer; + loadLdmlKeyboardTestSchema(): Buffer; + reportMessage(event: CompilerEvent): void; + loadKvksJsonSchema(): Buffer; + loadKpjJsonSchema(): Buffer; +}; + +/** + * Convenience function for constructing CompilerEvents + * @param code + * @param message + * @returns + */ +export const CompilerMessageSpec = (code: number, message: string) : CompilerEvent => { return { code, message } }; diff --git a/common/web/types/src/util/util.ts b/common/web/types/src/util/util.ts new file mode 100644 index 00000000000..7d70d001d4c --- /dev/null +++ b/common/web/types/src/util/util.ts @@ -0,0 +1,68 @@ +/** + * xml2js will not place single-entry objects into arrays. Easiest way to fix + * this is to box them ourselves as needed. Ensures that o.x is an array. + * + * @param o Object with property to box + * @param x Name of element to box + */ +export function boxXmlArray(o: any, x: string): void { + if(typeof o == 'object' && !Array.isArray(o[x])) { + if(o[x] === null || o[x] === undefined) { + o[x] = []; + } + else { + o[x] = [o[x]]; + } + } +} + +// TODO-LDML: #7569 the below regex works, but captures more than it should +// (it would include \u{fffffffffffffffff } which +// is overlong and has a space at the end.) The second regex does not work yet. +const MATCH_HEX_ESCAPE = /\\u{([0-9a-fA-F ]{1,})}/g; +// const MATCH_HEX_ESCAPE = /\\u{((?:(?:[0-9a-fA-F]{1,5})|(?:10[0-9a-fA-F]{4})(?: (?!}))?)+)}/g; + +export class UnescapeError extends Error { +} + +/** + * Unescapes a string according to UTS#18§1.1, see + * @param s escaped string + * @returns + */ +export function unescapeString(s: string): string { + if(!s) { + return s; + } + try { + /** + * Unescape one codepoint + * @param hex one codepoint in hex, such as '0127' + * @returns the unescaped codepoint + */ + function unescapeOne(hex: string) : string { + const codepoint = Number.parseInt(hex, 16); + return String.fromCodePoint(codepoint); + } + /** + * process one regex match + * @param str ignored + * @param matched the entire match such as '0127' or '22 22' + * @returns the unescaped match + */ + function processMatch(str: string, matched: string) : string { + const codepoints = matched.split(' '); + const unescaped = codepoints.map(unescapeOne); + return unescaped.join(''); + } + s = s.replaceAll(MATCH_HEX_ESCAPE, processMatch); + } catch(e) { + if (e instanceof RangeError) { + throw new UnescapeError(`Out of range while unescaping '${s}': ${e.message}`, { cause: e }); + } else { + throw e; + } + } + + return s; +} diff --git a/common/web/types/test/fixtures/import-minimal.xml b/common/web/types/test/fixtures/import-minimal.xml new file mode 100644 index 00000000000..500367699d1 --- /dev/null +++ b/common/web/types/test/fixtures/import-minimal.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/common/web/types/test/fixtures/import-minimal1.xml b/common/web/types/test/fixtures/import-minimal1.xml new file mode 100644 index 00000000000..439741d0415 --- /dev/null +++ b/common/web/types/test/fixtures/import-minimal1.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/common/web/types/test/fixtures/import-minimal2.xml b/common/web/types/test/fixtures/import-minimal2.xml new file mode 100644 index 00000000000..f04e32d6736 --- /dev/null +++ b/common/web/types/test/fixtures/import-minimal2.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/common/web/types/test/fixtures/import-symbols.xml b/common/web/types/test/fixtures/import-symbols.xml new file mode 100644 index 00000000000..9ca4638204b --- /dev/null +++ b/common/web/types/test/fixtures/import-symbols.xml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/common/web/types/test/fixtures/invalid-conforms-to.xml b/common/web/types/test/fixtures/invalid-conforms-to.xml new file mode 100644 index 00000000000..e2fdf8e4229 --- /dev/null +++ b/common/web/types/test/fixtures/invalid-conforms-to.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + diff --git a/common/web/types/test/fixtures/invalid-import-base.xml b/common/web/types/test/fixtures/invalid-import-base.xml new file mode 100644 index 00000000000..3b56f1feeb7 --- /dev/null +++ b/common/web/types/test/fixtures/invalid-import-base.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/common/web/types/test/fixtures/invalid-import-path.xml b/common/web/types/test/fixtures/invalid-import-path.xml new file mode 100644 index 00000000000..a9e670f8822 --- /dev/null +++ b/common/web/types/test/fixtures/invalid-import-path.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/common/web/types/test/fixtures/invalid-import-readfail.xml b/common/web/types/test/fixtures/invalid-import-readfail.xml new file mode 100644 index 00000000000..e254b553505 --- /dev/null +++ b/common/web/types/test/fixtures/invalid-import-readfail.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/common/web/types/test/fixtures/invalid-import-wrongroot.xml b/common/web/types/test/fixtures/invalid-import-wrongroot.xml new file mode 100644 index 00000000000..522e106e860 --- /dev/null +++ b/common/web/types/test/fixtures/invalid-import-wrongroot.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/common/web/types/test/fixtures/invalid-structure-per-dtd.xml b/common/web/types/test/fixtures/invalid-structure-per-dtd.xml new file mode 100644 index 00000000000..be504fdbf9e --- /dev/null +++ b/common/web/types/test/fixtures/invalid-structure-per-dtd.xml @@ -0,0 +1,8 @@ + + + + + diff --git a/common/web/types/test/fixtures/keyman-touch-layout/khmer_angkor.keyman-touch-layout b/common/web/types/test/fixtures/keyman-touch-layout/khmer_angkor.keyman-touch-layout new file mode 100644 index 00000000000..711f8490083 --- /dev/null +++ b/common/web/types/test/fixtures/keyman-touch-layout/khmer_angkor.keyman-touch-layout @@ -0,0 +1,1950 @@ +{ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "១" + }, + { + "id": "K_2", + "text": "២" + }, + { + "id": "K_3", + "text": "៣" + }, + { + "id": "K_4", + "text": "៤" + }, + { + "id": "K_5", + "text": "៥" + }, + { + "id": "K_6", + "text": "៦" + }, + { + "id": "K_7", + "text": "៧" + }, + { + "id": "K_8", + "text": "៨" + }, + { + "id": "K_9", + "text": "៩" + }, + { + "id": "K_0", + "text": "០" + }, + { + "id": "K_HYPHEN", + "text": "ឥ" + }, + { + "id": "K_EQUAL", + "text": "ឲ" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "រ" + }, + { + "id": "K_T", + "text": "ត" + }, + { + "id": "K_Y", + "text": "យ" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ផ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឪ" + }, + { + "id": "T_new_138", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "«" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "ស" + }, + { + "id": "K_D", + "text": "ដ" + }, + { + "id": "K_F", + "text": "ថ" + }, + { + "id": "K_G", + "text": "ង" + }, + { + "id": "K_H", + "text": "ហ" + }, + { + "id": "K_J", + "text": "" + }, + { + "id": "K_K", + "text": "ក" + }, + { + "id": "K_L", + "text": "ល" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឮ" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "ឋ" + }, + { + "id": "K_X", + "text": "ខ" + }, + { + "id": "K_C", + "text": "ច" + }, + { + "id": "K_V", + "text": "វ" + }, + { + "id": "K_B", + "text": "ប" + }, + { + "id": "K_N", + "text": "ន" + }, + { + "id": "K_M", + "text": "ម" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "។" + }, + { + "id": "K_SLASH", + "text": "" + }, + { + "id": "T_new_164", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "rightalt", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "‌" + }, + { + "id": "K_2", + "text": "@" + }, + { + "id": "K_3", + "text": "" + }, + { + "id": "K_4", + "text": "$" + }, + { + "id": "K_5", + "text": "€" + }, + { + "id": "K_6", + "text": "៙" + }, + { + "id": "K_7", + "text": "៚" + }, + { + "id": "K_8", + "text": "*" + }, + { + "id": "K_9", + "text": "{" + }, + { + "id": "K_0", + "text": "}" + }, + { + "id": "K_HYPHEN", + "text": "≈" + }, + { + "id": "K_EQUAL", + "text": "" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ៜ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "ឯ" + }, + { + "id": "K_R", + "text": "ឫ" + }, + { + "id": "K_T", + "text": "ឨ" + }, + { + "id": "K_Y", + "text": "[" + }, + { + "id": "K_U", + "text": "]" + }, + { + "id": "K_I", + "text": "ឦ" + }, + { + "id": "K_O", + "text": "ឱ" + }, + { + "id": "K_P", + "text": "ឰ" + }, + { + "id": "K_LBRKT", + "text": "ឩ" + }, + { + "id": "K_RBRKT", + "text": "ឳ" + }, + { + "id": "T_new_307", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "‍" + }, + { + "id": "K_A", + "text": "+" + }, + { + "id": "K_S", + "text": "-" + }, + { + "id": "K_D", + "text": "×" + }, + { + "id": "K_F", + "text": "÷" + }, + { + "id": "K_G", + "text": ":" + }, + { + "id": "K_H", + "text": "‘" + }, + { + "id": "K_J", + "text": "’" + }, + { + "id": "K_K", + "text": "ឝ" + }, + { + "id": "K_L", + "text": "៘" + }, + { + "id": "K_COLON", + "text": "៖" + }, + { + "id": "K_QUOTE", + "text": "ៈ" + }, + { + "id": "K_BKSLASH", + "text": "\\" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "<" + }, + { + "id": "K_X", + "text": ">" + }, + { + "id": "K_C", + "text": "#" + }, + { + "id": "K_V", + "text": "&" + }, + { + "id": "K_B", + "text": "ឞ" + }, + { + "id": "K_N", + "text": ";" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "," + }, + { + "id": "K_PERIOD", + "text": "." + }, + { + "id": "K_SLASH", + "text": "/" + }, + { + "id": "T_new_333", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": " ", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "!" + }, + { + "id": "K_2", + "text": "ៗ" + }, + { + "id": "K_3", + "text": "\"" + }, + { + "id": "K_4", + "text": "៛" + }, + { + "id": "K_5", + "text": "%" + }, + { + "id": "K_6", + "text": "" + }, + { + "id": "K_7", + "text": "" + }, + { + "id": "K_8", + "text": "" + }, + { + "id": "K_9", + "text": "(" + }, + { + "id": "K_0", + "text": ")" + }, + { + "id": "K_HYPHEN", + "text": "" + }, + { + "id": "K_EQUAL", + "text": "=" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ឈ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "ឬ" + }, + { + "id": "K_T", + "text": "ទ" + }, + { + "id": "K_Y", + "text": "" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ភ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឧ" + }, + { + "id": "T_new_364", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "»" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "" + }, + { + "id": "K_D", + "text": "ឌ" + }, + { + "id": "K_F", + "text": "ធ" + }, + { + "id": "K_G", + "text": "អ" + }, + { + "id": "K_H", + "text": "ះ" + }, + { + "id": "K_J", + "text": "ញ" + }, + { + "id": "K_K", + "text": "គ" + }, + { + "id": "K_L", + "text": "ឡ" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឭ" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "ឍ" + }, + { + "id": "K_X", + "text": "ឃ" + }, + { + "id": "K_C", + "text": "ជ" + }, + { + "id": "K_V", + "text": "" + }, + { + "id": "K_B", + "text": "ព" + }, + { + "id": "K_N", + "text": "ណ" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "៕" + }, + { + "id": "K_SLASH", + "text": "?" + }, + { + "id": "T_new_390", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + } + ], + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + }, + "phone": { + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "", + "sk": [ + { + "text": "ឈ", + "id": "K_Q", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1786" + }, + { + "text": "", + "id": "T_17D2_1788" + } + ] + }, + { + "id": "K_W", + "text": "", + "sk": [ + { + "text": "", + "id": "K_W", + "layer": "shift" + } + ] + }, + { + "id": "K_E", + "text": "", + "sk": [ + { + "text": "", + "id": "K_E", + "layer": "shift" + }, + { + "text": "", + "id": "K_S", + "layer": "shift" + }, + { + "text": "", + "id": "K_V", + "layer": "shift" + }, + { + "text": "ឯ", + "id": "U_17AF" + }, + { + "text": "ឰ", + "id": "U_17B0" + } + ] + }, + { + "id": "K_R", + "text": "រ", + "sk": [ + { + "text": "", + "id": "T_17D2_179A" + }, + { + "text": "ឫ", + "id": "U_17AB" + }, + { + "text": "ឬ", + "id": "U_17AC" + } + ] + }, + { + "id": "K_T", + "text": "ត", + "sk": [ + { + "text": "ទ", + "id": "K_T", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178F" + }, + { + "text": "", + "id": "T_17D2_1791", + "layer": "default" + } + ] + }, + { + "id": "K_Y", + "text": "យ", + "sk": [ + { + "text": "", + "id": "T_17D2_1799" + } + ] + }, + { + "id": "K_U", + "text": "", + "sk": [ + { + "text": "", + "id": "K_U", + "layer": "shift" + }, + { + "text": "", + "id": "K_Y", + "layer": "shift" + }, + { + "text": "ឧ", + "id": "U_17A7" + }, + { + "text": "ឪ", + "id": "U_17AA", + "layer": "shift" + }, + { + "text": "ឩ", + "id": "U_17A9", + "layer": "shift" + }, + { + "text": "ឨ", + "id": "U_17A8" + } + ] + }, + { + "id": "K_I", + "text": "", + "sk": [ + { + "text": "", + "id": "K_I", + "layer": "shift" + }, + { + "text": "ឥ", + "id": "U_17A5" + }, + { + "text": "ឦ", + "id": "U_17A6", + "layer": "shift" + } + ] + }, + { + "id": "K_O", + "text": "", + "sk": [ + { + "text": "", + "id": "K_O", + "layer": "shift" + }, + { + "text": "", + "id": "K_LBRKT" + }, + { + "text": "", + "id": "K_LBRKT", + "layer": "shift" + }, + { + "text": "", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": "ឱ", + "id": "U_17B1" + }, + { + "text": "ឲ", + "id": "U_17B2" + }, + { + "text": "ឳ", + "id": "U_17B3", + "layer": "shift" + } + ] + }, + { + "id": "K_P", + "text": "ផ", + "sk": [ + { + "text": "ភ", + "id": "K_P", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1795" + }, + { + "text": "", + "id": "T_17D2_1797", + "layer": "default" + } + ] + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "", + "pad": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_A", + "layer": "shift" + } + ] + }, + { + "id": "K_S", + "text": "ស", + "sk": [ + { + "text": "", + "id": "T_17D2_179F" + }, + { + "text": "ឝ", + "id": "U_179D" + }, + { + "text": "ឞ", + "id": "U_179E" + } + ] + }, + { + "id": "K_D", + "text": "ដ", + "sk": [ + { + "text": "ឌ", + "id": "K_D", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178A" + }, + { + "text": "", + "id": "T_17D2_178C", + "layer": "default" + } + ] + }, + { + "id": "K_F", + "text": "ថ", + "sk": [ + { + "text": "ធ", + "id": "K_F", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1790" + }, + { + "text": "", + "id": "T_17D2_1792", + "layer": "default" + } + ] + }, + { + "id": "K_G", + "text": "ង", + "sk": [ + { + "text": "អ", + "id": "K_G", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1784" + }, + { + "text": "", + "id": "T_17D2_17A2", + "layer": "default" + } + ] + }, + { + "id": "K_H", + "text": "ហ", + "sk": [ + { + "text": "", + "id": "T_17D2_17A0" + }, + { + "text": "ះ", + "id": "K_H", + "layer": "shift" + }, + { + "text": "ៈ", + "id": "U_17C8" + } + ] + }, + { + "id": "K_J", + "text": "ញ", + "layer": "shift", + "sk": [ + { + "text": "", + "id": "T_17D2_1789" + } + ] + }, + { + "id": "K_K", + "text": "ក", + "sk": [ + { + "text": "គ", + "id": "K_K", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1780" + }, + { + "text": "", + "id": "T_17D2_1782" + } + ] + }, + { + "id": "K_L", + "text": "ល", + "sk": [ + { + "text": "ឡ", + "id": "K_L", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_179B" + }, + { + "text": "ឭ", + "id": "U_17AD" + }, + { + "text": "ឮ", + "id": "U_17AE" + } + ] + }, + { + "id": "K_COLON", + "text": "" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_Z", + "text": "ឋ", + "pad": "", + "width": "", + "sk": [ + { + "text": "ឍ", + "id": "K_Z", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178B" + }, + { + "text": "", + "id": "T_17D2_178D", + "layer": "default" + } + ] + }, + { + "id": "K_X", + "text": "ខ", + "sk": [ + { + "text": "ឃ", + "id": "K_X", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1781" + }, + { + "text": "", + "id": "T_17D2_1783", + "layer": "default" + } + ] + }, + { + "id": "K_C", + "text": "ច", + "sk": [ + { + "text": "ជ", + "id": "K_C", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1785" + }, + { + "text": "", + "id": "T_17D2_1787", + "layer": "default" + } + ] + }, + { + "id": "K_V", + "text": "វ", + "sk": [ + { + "text": "", + "id": "T_17D2_179C" + } + ] + }, + { + "id": "K_B", + "text": "ប", + "sk": [ + { + "text": "ព", + "id": "K_B", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1794" + }, + { + "text": "", + "id": "T_17D2_1796", + "layer": "default" + } + ] + }, + { + "id": "K_N", + "text": "ន", + "sk": [ + { + "text": "ណ", + "id": "K_N", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1793" + }, + { + "text": "", + "id": "T_17D2_178E", + "layer": "default" + } + ] + }, + { + "id": "K_M", + "text": "ម", + "sk": [ + { + "text": "", + "id": "T_17D2_1798" + }, + { + "text": "", + "id": "K_M", + "layer": "shift" + } + ] + }, + { + "id": "K_COMMA", + "text": "", + "sk": [ + { + "text": "", + "id": "K_COMMA", + "layer": "shift" + }, + { + "text": "", + "id": "K_6", + "layer": "shift" + }, + { + "text": "", + "id": "K_7", + "layer": "shift" + }, + { + "text": "", + "id": "K_8", + "layer": "shift" + }, + { + "text": "", + "id": "K_HYPHEN", + "layer": "shift" + }, + { + "text": "", + "id": "U_17D1", + "layer": "shift" + }, + { + "text": "", + "id": "U_17DD", + "layer": "shift" + }, + { + "text": "", + "id": "U_17CE", + "layer": "shift" + } + ] + }, + { + "id": "K_QUOTE", + "text": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "", + "id": "K_SLASH" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "១២៣", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "sp": "0", + "sk": [ + { + "text": " ", + "id": "U_0020", + "layer": "default" + } + ] + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "១", + "pad": "", + "sk": [ + { + "text": "1", + "id": "U_0031" + } + ] + }, + { + "id": "K_2", + "text": "២", + "sk": [ + { + "text": "2", + "id": "U_0032" + } + ] + }, + { + "id": "K_3", + "text": "៣", + "sk": [ + { + "text": "3", + "id": "U_0033" + } + ] + }, + { + "id": "K_4", + "text": "៤", + "sk": [ + { + "text": "4", + "id": "U_0034" + } + ] + }, + { + "id": "K_5", + "text": "៥", + "sk": [ + { + "text": "5", + "id": "U_0035" + } + ] + }, + { + "id": "K_6", + "text": "៦", + "sk": [ + { + "text": "6", + "id": "U_0036" + } + ] + }, + { + "id": "K_7", + "text": "៧", + "sk": [ + { + "text": "7", + "id": "U_0037" + } + ] + }, + { + "id": "K_8", + "text": "៨", + "sk": [ + { + "text": "8", + "id": "U_0038" + } + ] + }, + { + "id": "K_9", + "text": "៩", + "sk": [ + { + "text": "9", + "id": "U_0039" + } + ] + }, + { + "id": "K_0", + "text": "០", + "sk": [ + { + "text": "0", + "id": "U_0030" + }, + { + "text": "", + "id": "U_17D3" + } + ] + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "U_0040", + "text": "@", + "pad": "", + "sk": [ + { + "text": "©", + "id": "U_00A9" + }, + { + "text": "®", + "id": "U_00AE" + } + ] + }, + { + "id": "U_0023", + "text": "#", + "sk": [ + { + "text": "№", + "id": "U_2116" + }, + { + "text": "~", + "id": "U_007E" + } + ] + }, + { + "id": "U_17DB", + "text": "៛", + "sk": [ + { + "text": "$", + "id": "U_0024" + }, + { + "text": "฿", + "id": "U_0E3F" + }, + { + "text": "¢", + "id": "U_00A2" + }, + { + "text": "£", + "id": "U_00A3" + }, + { + "text": "¥", + "id": "U_00A5" + } + ] + }, + { + "id": "U_0026", + "text": "&" + }, + { + "id": "U_0025", + "text": "%", + "sk": [ + { + "text": "‰", + "id": "U_2030" + }, + { + "text": "‱", + "id": "U_2031" + } + ] + }, + { + "id": "U_002B", + "text": "+", + "sk": [ + { + "text": "-", + "id": "U_002D" + }, + { + "text": "×", + "id": "U_00D7" + }, + { + "text": "÷", + "id": "U_00F7" + }, + { + "text": "±", + "id": "U_00B1" + } + ] + }, + { + "id": "U_003D", + "text": "=", + "sk": [ + { + "text": "_", + "id": "U_005F" + }, + { + "text": "≠", + "id": "U_2260" + } + ] + }, + { + "id": "U_002A", + "text": "*", + "sk": [ + { + "text": "^", + "id": "U_005E" + } + ] + }, + { + "id": "U_003F", + "text": "?", + "sk": [ + { + "text": "¿", + "id": "U_00BF" + } + ] + }, + { + "id": "U_0021", + "text": "!", + "sk": [ + { + "text": "¡", + "id": "U_00A1" + } + ] + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "U_2018", + "text": "‘", + "sk": [ + { + "text": "’", + "id": "U_2019" + } + ] + }, + { + "id": "U_201C", + "text": "“", + "sk": [ + { + "text": "”", + "id": "U_201D" + } + ] + }, + { + "id": "U_00AB", + "text": "«", + "pad": "", + "sk": [ + { + "text": "»", + "id": "U_00BB" + } + ] + }, + { + "id": "U_002F", + "text": "/", + "sk": [ + { + "text": "\\", + "id": "U_005C" + }, + { + "text": "|", + "id": "U_007C" + }, + { + "text": "¦", + "id": "U_00A6" + } + ] + }, + { + "id": "U_0028", + "text": "(", + "sk": [ + { + "text": ")", + "id": "U_0029" + }, + { + "text": "[", + "id": "U_005B" + }, + { + "text": "]", + "id": "U_005D" + }, + { + "text": "{", + "id": "U_007B" + }, + { + "text": "}", + "id": "U_007D" + } + ] + }, + { + "id": "U_17D9", + "text": "៙", + "sk": [ + { + "text": "៚", + "id": "U_17DA" + }, + { + "text": "ៜ", + "id": "U_17DC" + }, + { + "text": "§", + "id": "U_00A7" + }, + { + "text": "Ø", + "id": "U_00D8" + } + ] + }, + { + "id": "U_17D7", + "text": "ៗ" + }, + { + "id": "U_003C", + "text": "<", + "sk": [ + { + "text": "≤", + "id": "U_2264" + }, + { + "text": ">", + "id": "U_003E" + }, + { + "text": "≥", + "id": "U_2265" + } + ] + }, + { + "id": "U_17D6", + "text": "៖", + "sk": [ + { + "text": ":", + "id": "U_003A" + }, + { + "text": ";", + "id": "U_003B" + }, + { + "text": "…", + "id": "U_2026" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LCONTROL", + "text": "១២៣", + "width": "140", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "sp": "0", + "layer": "shift", + "sk": [] + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + } + ], + "displayUnderlying": false, + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + } +} \ No newline at end of file diff --git a/common/web/types/test/fixtures/keyman-touch-layout/legacy.keyman-touch-layout b/common/web/types/test/fixtures/keyman-touch-layout/legacy.keyman-touch-layout new file mode 100644 index 00000000000..27b88076c45 --- /dev/null +++ b/common/web/types/test/fixtures/keyman-touch-layout/legacy.keyman-touch-layout @@ -0,0 +1,36 @@ +{ + "tablet": { + "layer": [ + { + "id": "default", + "row": [ + { + "id": "1", + "key": [ + { + "id": "K_BKSP", + "text": "*BkSp*", + "pad": "25", + "width": "100", + "sp": "1", + "sk": [ + { + "id": "K_BAM", + "text": "Bam", + "pad": "10", + "width": "25", + "sp": "8" + } + ] + }, + { + "id": "K_FOO", + "sk": [] + } + ] + } + ] + } + ] + } +} diff --git a/common/web/types/test/fixtures/kpj/khmer_angkor.kpj b/common/web/types/test/fixtures/kpj/khmer_angkor.kpj new file mode 100644 index 00000000000..4b239df20ce --- /dev/null +++ b/common/web/types/test/fixtures/kpj/khmer_angkor.kpj @@ -0,0 +1,187 @@ + + + + $PROJECTPATH\build + True + True + False + keyboard + + + + id_f347675c33d2e6b1c705c787fad4941a + khmer_angkor.kmn + source\khmer_angkor.kmn + 1.3 + .kmn +
+ Khmer Angkor + © 2015-2022 SIL International + More than just a Khmer Unicode keyboard. +
+
+ + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + khmer_angkor.kps + source\khmer_angkor.kps + + .kps +
+ Khmer Angkor + © 2015-2022 SIL International +
+
+ + id_8a1efc7c4ab7cfece8aedd847679ca27 + khmer_angkor.ico + source\khmer_angkor.ico + + .ico + id_f347675c33d2e6b1c705c787fad4941a + + + id_8dc195db32d1fd0514de0ad51fff5df0 + khmer_angkor.js + source\..\build\khmer_angkor.js + + .js + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_10596632fcbf4138d24bcccf53e6ae01 + khmer_angkor.kvk + source\..\build\khmer_angkor.kvk + + .kvk + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_0a851f95ce553ecd62cbee6c32ced68f + khmer_angkor.kmx + source\..\build\khmer_angkor.kmx + + .kmx + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_d8b6eb05f4b7e2945c10e04c1f49e4c8 + keyboard_layout.png + source\welcome\keyboard_layout.png + + .png + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_724e5b4c63f10bc0abf7077f7c3172fc + welcome.htm + source\welcome\welcome.htm + + .htm + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_35857cb2b54f123612735ec948400082 + FONTLOG.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\FONTLOG.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_7e3afe5bb59b888b08b48cd5817d8de4 + Mondulkiri-B.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-B.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_b9734e80f86c69ea5ae4dfa9f0083d09 + Mondulkiri-BI.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-BI.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_25abe4d2b0abc03a5be5b666a8de776e + Mondulkiri-I.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-I.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_b766568498108eee46ed1601ff69c47d + Mondulkiri-R.ttf + source\..\..\..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_84544d04133cab3dbfc86b91ad1a4e17 + OFL.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\OFL.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_0c33fbefd1c20f487b1bea2343b3bb2c + OFL-FAQ.txt + source\..\..\..\shared\fonts\khmer\mondulkiri\OFL-FAQ.txt + + .txt + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_a59d89fca36a310147645fa2604e521b + KAK_Documentation_EN.pdf + source\welcome\KAK_Documentation_EN.pdf + + .pdf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_5643c4cd3933b3ada0b4af6579305ec4 + KAK_Documentation_KH.pdf + source\welcome\KAK_Documentation_KH.pdf + + .pdf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_8da344c4cea6f467013357fe099006f5 + readme.htm + source\readme.htm + + .htm + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_acb0dd94c60e345d999670e999cbd159 + image002.png + source\welcome\image002.png + + .png + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_4edf70bc019f05b5ad39a2ea727ad547 + khmer_busra_kbd.ttf + source\..\..\..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf + + .ttf + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + + + id_bc823844e4399751e1867016801f7327 + splash.gif + source\splash.gif + + .gif + id_8d4eb765f80c9f2b0f769cf4e4aaa456 + +
+
diff --git a/common/web/types/test/fixtures/kvk/khmer_angkor.kvk b/common/web/types/test/fixtures/kvk/khmer_angkor.kvk new file mode 100644 index 00000000000..c64b3a5a544 Binary files /dev/null and b/common/web/types/test/fixtures/kvk/khmer_angkor.kvk differ diff --git a/common/web/types/test/fixtures/kvk/khmer_angkor.kvks b/common/web/types/test/fixtures/kvk/khmer_angkor.kvks new file mode 100644 index 00000000000..e9c38a464dd --- /dev/null +++ b/common/web/types/test/fixtures/kvk/khmer_angkor.kvks @@ -0,0 +1,206 @@ + + +
+ 10.0 + khmer_angkor + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + ] + [ + / + . + + + + & + + * + @ + \ + } + { + - + ÷ + : + , + + ; + < + # + > + × + $ + +   + + + + + + + + + + + + + ᧿ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + « + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + " + + % + + ( + ) + + = + + + ? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + + » + + + +
diff --git a/common/web/types/test/fixtures/test-fr.xml b/common/web/types/test/fixtures/test-fr.xml new file mode 100644 index 00000000000..c97825385ad --- /dev/null +++ b/common/web/types/test/fixtures/test-fr.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/common/web/types/test/helpers/index.ts b/common/web/types/test/helpers/index.ts new file mode 100644 index 00000000000..a6848021ef7 --- /dev/null +++ b/common/web/types/test/helpers/index.ts @@ -0,0 +1,39 @@ +import path from "path"; +import fs from "fs"; +import { fileURLToPath } from "url"; + +/** + * Builds a path to the fixture with the given path components. + * + * e.g., makePathToFixture('basic.xml') + * + * @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)); +} + +export function loadKvksJsonSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'kvks.schema.json'), import.meta.url)); +} + +export function loadKeymanTouchLayoutCleanJsonSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'keyman-touch-layout.clean.spec.json'), import.meta.url)); +} + +export function loadFile(baseFilename: string, filename: string | URL): Buffer { + // TODO: translate filename based on the baseFilename + return fs.readFileSync(filename); +} + +export function loadLdmlKeyboardSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'ldml-keyboard.schema.json'), import.meta.url)); +} + +export function loadLdmlKeyboardTestDataSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'ldml-keyboardtest.schema.json'), import.meta.url)); +} + +export function loadKpjJsonSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'kpj.schema.json'), import.meta.url)); +} diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/common/web/types/test/helpers/reader-callback-test.ts new file mode 100644 index 00000000000..7d8d7d113b7 --- /dev/null +++ b/common/web/types/test/helpers/reader-callback-test.ts @@ -0,0 +1,195 @@ +import 'mocha'; +import {assert} from 'chai'; +import {loadLdmlKeyboardSchema, loadFile, makePathToFixture, loadLdmlKeyboardTestDataSchema} from '../helpers/index.js'; +import LDMLKeyboardXMLSourceFileReader from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; +import { CompilerCallbacks, CompilerEvent } from '../../src/util/compiler-interfaces.js'; +import { LDMLKeyboardXMLSourceFile } from '../../src/ldml-keyboard/ldml-keyboard-xml.js'; +import { LDMLKeyboardTestDataXMLSourceFile } from '../ldml-keyboard/ldml-keyboard-testdata-xml.js'; + +// TODO-LDML: this is largely a port from developer/src/kmc-keyboard/test/helpers/index.ts + +/** + * A CompilerCallbacks implementation for testing + */ +class TestCompilerCallbacks implements CompilerCallbacks { + loadLdmlKeyboardTestSchema(): Buffer { + return loadLdmlKeyboardTestDataSchema(); + } + loadLdmlKeyboardSchema(): Buffer { + return loadLdmlKeyboardSchema(); + } + loadKvksJsonSchema(): Buffer { + throw new Error('loadKvksJsonSchema not implemented.'); + } + clear() { + this.messages = []; + } + messages: CompilerEvent[] = []; + loadFile(baseFilename: string, filename: string | URL): Buffer { + try { + return loadFile(baseFilename, filename); + } catch(e) { + if (e.code === 'ENOENT') { + return null; + } else { + throw e; + } + } + } + reportMessage(event: CompilerEvent): void { + // console.log(event.message); + this.messages.push(event); + } +} + +export interface CompilationCase { + /** + * If true, loading (validation) should fail + */ + loadfail?: boolean; + /** + * path to xml, such as 'sections/layr/invalid-case.xml' + */ + subpath: string; + /** + * expected error messages. If falsy, expected to succeed. All must be present to pass. + */ + errors?: CompilerEvent[]; + /** + * expected warning messages. All must be present to pass. + */ + warnings?: CompilerEvent[]; + /** + * optional callback with the section + */ + callback?: (data: Buffer, source: LDMLKeyboardXMLSourceFile, subpath: string, callbacks: TestCompilerCallbacks ) => void; + /** + * if present, expect compiler to throw (use .* to match all) + */ + throws?: RegExp; +} + + +export interface TestDataCase { + /** + * If true, loading (validation) should fail + */ + loadfail?: boolean; + /** + * path to xml, such as 'sections/layr/invalid-case.xml' + */ + subpath: string; + /** + * expected error messages. If falsy, expected to succeed. All must be present to pass. + */ + errors?: CompilerEvent[]; + /** + * expected warning messages. All must be present to pass. + */ + warnings?: CompilerEvent[]; + /** + * optional callback with the section + */ + callback?: (data: Buffer, source: LDMLKeyboardTestDataXMLSourceFile, subpath: string, callbacks: TestCompilerCallbacks ) => void; + /** + * if present, expect compiler to throw (use .* to match all) + */ + throws?: RegExp; +} + +/** + * Run a bunch of cases + * @param cases cases to run + * @param compiler argument to loadSectionFixture() + * @param callbacks argument to loadSectionFixture() + */ +export function testReaderCases(cases : CompilationCase[]) { + // we need our own callbacks rather than using the global so messages don't get mixed + const callbacks = new TestCompilerCallbacks(); + const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + for (let testcase of cases) { + const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail + const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`: + `should load: ${testcase.subpath}`; + it(testHeading, function () { + callbacks.clear(); + + const data = loadFile(testcase.subpath, makePathToFixture(testcase.subpath)); + assert.ok(data, `reading ${testcase.subpath}`); + const source = reader.load(data); + if (!testcase.loadfail) { + assert.ok(source, `loading ${testcase.subpath}`); + } else { + assert.notOk(source, `loading ${testcase.subpath} (expected failure)`); + } + // special case for an expected exception + if (testcase.throws) { + assert.throws(() => reader.validate(source, loadLdmlKeyboardSchema()), testcase.throws); + } else { + assert.doesNotThrow(() => reader.validate(source, loadLdmlKeyboardSchema()), `validating ${testcase.subpath}`); + // if we expected errors or warnings, show them + if (testcase.errors) { + assert.includeDeepMembers(callbacks.messages, testcase.errors, 'expected errors to be included'); + } + if (testcase.warnings) { + assert.includeDeepMembers(callbacks.messages, testcase.warnings, 'expected warnings to be included'); + } else if (!expectFailure) { + // no warnings, so expect zero messages + assert.strictEqual(callbacks.messages.length, 0, 'expected zero messages'); + } + + // run the user-supplied callback if any + if (testcase.callback) { + testcase.callback(data, source, testcase.subpath, callbacks); + } + } + }); + } +} + + +/** + * Run a bunch of cases + * @param cases cases to run + * @param compiler argument to loadSectionFixture() + * @param callbacks argument to loadSectionFixture() + */ +export function testTestdataReaderCases(cases : TestDataCase[]) { + // we need our own callbacks rather than using the global so messages don't get mixed + const callbacks = new TestCompilerCallbacks(); + const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + for (let testcase of cases) { + const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail + const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`: + `should load: ${testcase.subpath}`; + it(testHeading, function () { + callbacks.clear(); + + const data = loadFile(testcase.subpath, makePathToFixture(testcase.subpath)); + assert.ok(data, `reading ${testcase.subpath}`); + const source = reader.loadTestData(data); + if (!testcase.loadfail) { + assert.ok(source, `loading ${testcase.subpath}`); + } else { + assert.notOk(source, `loading ${testcase.subpath} (expected failure)`); + } + // special case for an expected exception + // TODO-LDML: no validation for now. + // if we expected errors or warnings, show them + if (testcase.errors) { + assert.includeDeepMembers(callbacks.messages, testcase.errors, 'expected errors to be included'); + } + if (testcase.warnings) { + assert.includeDeepMembers(callbacks.messages, testcase.warnings, 'expected warnings to be included'); + } else if (!expectFailure) { + // no warnings, so expect zero messages + assert.strictEqual(callbacks.messages.length, 0, 'expected zero messages but got ' +callbacks.messages.map(e => e.message).join(' ') ); + } + + // run the user-supplied callback if any + if (testcase.callback) { + testcase.callback(data, source, testcase.subpath, callbacks); + } + }); + } +} diff --git a/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts b/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts new file mode 100644 index 00000000000..35a3d8626d5 --- /dev/null +++ b/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-file-reader.ts @@ -0,0 +1,71 @@ +import * as fs from 'fs'; +import 'mocha'; +import { assert } from 'chai'; +import { loadKeymanTouchLayoutCleanJsonSchema, makePathToFixture } from '../helpers/index.js'; +import { TouchLayoutFileReader } from "../../src/keyman-touch-layout/keyman-touch-layout-file-reader.js"; + +describe('TouchLayoutFileReader', function () { + it('should read a valid file', function() { + const path = makePathToFixture('keyman-touch-layout', 'khmer_angkor.keyman-touch-layout'); + const schema = loadKeymanTouchLayoutCleanJsonSchema(); + const input = fs.readFileSync(path); + const reader = new TouchLayoutFileReader(); + const layout = reader.read(input); + reader.validate(layout, schema); + + // We don't really need to validate the JSON parser. We'll do a handful of + // tests for object integrity and a couple of object members inside the file + // just as a sanity check + assert.hasAllKeys(layout, ['tablet', 'phone']); + assert.hasAllKeys(layout.tablet, ['layer', 'displayUnderlying', 'font', 'fontsize']); + assert.isFalse(layout.tablet.displayUnderlying); + assert.strictEqual(layout.tablet.font, 'Khmer Busra Kbd'); + assert.strictEqual(layout.tablet.fontsize, '0.8em'); + assert.isArray(layout.tablet.layer); + assert.strictEqual(layout.tablet.layer.length, 3); + assert.strictEqual(layout.tablet.layer[0].id, 'default'); + assert.isArray(layout.tablet.layer[0].row); + assert.isArray(layout.tablet.layer[0].row); + assert.strictEqual(layout.tablet.layer[0].row.length, 5); + assert.strictEqual(layout.tablet.layer[0].row[0].id, 1); + assert.isArray(layout.tablet.layer[0].row[0].key); + assert.strictEqual(layout.tablet.layer[0].row[0].key.length, 13); + assert.strictEqual(layout.tablet.layer[0].row[0].key[0].id, "K_1"); + assert.strictEqual(layout.tablet.layer[0].row[0].key[0].text, "១"); + }); + + it('should fixup numeric types in a valid legacy file', function() { + const path = makePathToFixture('keyman-touch-layout', 'legacy.keyman-touch-layout'); + const schema = loadKeymanTouchLayoutCleanJsonSchema(); + const input = fs.readFileSync(path); + const reader = new TouchLayoutFileReader(); + const layout = reader.read(input); + reader.validate(layout, schema); + + // row.id can be a string in legacy files + assert.strictEqual(layout.tablet.layer[0].row[0].id, 1); + + // key pad, width and sp can be a string in legacy files + assert.strictEqual(layout.tablet.layer[0].row[0].key[0].pad, 25); + assert.strictEqual(layout.tablet.layer[0].row[0].key[0].width, 100); + assert.strictEqual(layout.tablet.layer[0].row[0].key[0].sp, 1); + + // sk pad, width and sp can be a string in legacy files + assert.strictEqual(layout.tablet.layer[0].row[0].key[0].sk[0].pad, 10); + assert.strictEqual(layout.tablet.layer[0].row[0].key[0].sk[0].width, 25); + assert.strictEqual(layout.tablet.layer[0].row[0].key[0].sk[0].sp, 8); + }); + + it('should fixup remove empty arrays in a valid legacy file', function() { + const path = makePathToFixture('keyman-touch-layout', 'legacy.keyman-touch-layout'); + const schema = loadKeymanTouchLayoutCleanJsonSchema(); + const input = fs.readFileSync(path); + const reader = new TouchLayoutFileReader(); + const layout = reader.read(input); + reader.validate(layout, schema); + + // sk was defined as [] in legacy.keyman-touch-layout but we want to treat + // it as not present + assert.isUndefined(layout.tablet.layer[0].row[0].key[1].sk); + }); +}); diff --git a/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts b/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts new file mode 100644 index 00000000000..f6a09d08d3b --- /dev/null +++ b/common/web/types/test/keyman-touch-layout/test-keyman-touch-layout-round-trip.ts @@ -0,0 +1,35 @@ +import * as fs from 'fs'; +import 'mocha'; +import { assert } from 'chai'; +import { makePathToFixture } from '../helpers/index.js'; +import { TouchLayoutFileReader } from "../../src/keyman-touch-layout/keyman-touch-layout-file-reader.js"; +import { TouchLayoutFileWriter } from "../../src/keyman-touch-layout/keyman-touch-layout-file-writer.js"; + +describe('TouchLayoutFile', function () { + it('should round-trip from TouchLayoutFileReader to TouchLayoutFileWriter', function() { + const path = makePathToFixture('keyman-touch-layout', 'khmer_angkor.keyman-touch-layout'); + const input = fs.readFileSync(path); + const reader = new TouchLayoutFileReader(); + const layout = reader.read(input); + const writer = new TouchLayoutFileWriter(); + + // We don't want to assert equality on formatting differences, or on the + // fixups that we do on input files (empty arrays, stringified numbers), so + // we'll re-read the output and compare it + let output = writer.write(layout); + let newLayout = reader.read(output); + assert.deepEqual(layout, newLayout); + + // And do the same without formatted output + output = writer.write(layout); + newLayout = reader.read(output); + assert.deepEqual(layout, newLayout); + + // And do the same without any options + let output2 = writer.write(layout); + assert.deepEqual(output, output2); + + newLayout = reader.read(output2); + assert.deepEqual(layout, newLayout); + }); +}); diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/common/web/types/test/kpj/test-kpj-file-reader.ts new file mode 100644 index 00000000000..2573b3ace74 --- /dev/null +++ b/common/web/types/test/kpj/test-kpj-file-reader.ts @@ -0,0 +1,120 @@ +import * as fs from 'fs'; +import 'mocha'; +import {assert} from 'chai'; +import { loadKpjJsonSchema, makePathToFixture } from '../helpers/index.js'; +import { KPJFileReader } from "../../src/kpj/kpj-file-reader.js"; +import { KeymanDeveloperProjectType } from '../../src/kpj/keyman-developer-project.js'; + +describe('kpj-file-reader', function () { + it('kpj-file-reader should read a valid file', function() { + const path = makePathToFixture('kpj', 'khmer_angkor.kpj'); + const input = fs.readFileSync(path); + const reader = new KPJFileReader(); + const kpj = reader.read(input); + console.dir(kpj); + assert.doesNotThrow(() => { + reader.validate(kpj, loadKpjJsonSchema()); + }); + assert.equal(kpj.KeymanDeveloperProject.Options.BuildPath, '$PROJECTPATH\\build'); + assert.equal(kpj.KeymanDeveloperProject.Options.CheckFilenameConventions, 'False'); + assert.equal(kpj.KeymanDeveloperProject.Options.CompilerWarningsAsErrors, 'True'); + assert.equal(kpj.KeymanDeveloperProject.Options.ProjectType, 'keyboard'); + assert.equal(kpj.KeymanDeveloperProject.Options.WarnDeprecatedCode, 'True'); + assert.isUndefined(kpj.KeymanDeveloperProject.Options.Version); + + assert.lengthOf(kpj.KeymanDeveloperProject.Files.File, 21); + + // Test a representative set of files - kmn, kps, 2 child files + + let kf = kpj.KeymanDeveloperProject.Files.File[0]; + assert.equal(kf.ID, 'id_f347675c33d2e6b1c705c787fad4941a'); + assert.equal(kf.Filename, 'khmer_angkor.kmn'); + assert.equal(kf.Filepath, 'source\\khmer_angkor.kmn'); + assert.equal(kf.FileVersion, '1.3'); + assert.equal(kf.FileType, '.kmn'); + assert.equal(kf.Details.Name, 'Khmer Angkor'); + assert.equal(kf.Details.Copyright, '© 2015-2022 SIL International'); + assert.equal(kf.Details.Message, 'More than just a Khmer Unicode keyboard.'); + + kf = kpj.KeymanDeveloperProject.Files.File[1]; + assert.equal(kf.ID, 'id_8d4eb765f80c9f2b0f769cf4e4aaa456'); + assert.equal(kf.Filename, 'khmer_angkor.kps'); + assert.equal(kf.Filepath, 'source\\khmer_angkor.kps'); + assert.isEmpty(kf.FileVersion); + assert.equal(kf.FileType, '.kps'); + assert.equal(kf.Details.Name, 'Khmer Angkor'); + assert.equal(kf.Details.Copyright, '© 2015-2022 SIL International'); + + kf = kpj.KeymanDeveloperProject.Files.File[2]; + assert.equal(kf.ID, 'id_8a1efc7c4ab7cfece8aedd847679ca27'); + assert.equal(kf.Filename, 'khmer_angkor.ico'); + assert.equal(kf.Filepath, 'source\\khmer_angkor.ico'); + assert.isEmpty(kf.FileVersion); + assert.equal(kf.FileType, '.ico'); + assert.equal(kf.ParentFileID, 'id_f347675c33d2e6b1c705c787fad4941a'); + + kf = kpj.KeymanDeveloperProject.Files.File[3]; + assert.equal(kf.ID, 'id_8dc195db32d1fd0514de0ad51fff5df0'); + assert.equal(kf.Filename, 'khmer_angkor.js'); + assert.equal(kf.Filepath, 'source\\..\\build\\khmer_angkor.js'); + assert.isEmpty(kf.FileVersion); + assert.equal(kf.FileType, '.js'); + assert.equal(kf.ParentFileID, 'id_8d4eb765f80c9f2b0f769cf4e4aaa456'); + + // Test transform of .kpj into a KeymanDeveloperProject + + const project = reader.transform(kpj); + + assert.equal(project.options.buildPath, '$PROJECTPATH/build'); + assert.isFalse(project.options.checkFilenameConventions); + assert.isTrue(project.options.compilerWarningsAsErrors); + assert.equal(project.options.projectType, KeymanDeveloperProjectType.Keyboard); + assert.isTrue(project.options.warnDeprecatedCode); + assert.equal(project.options.version, '1.0'); + + assert.lengthOf(project.files, 2); + + let f = project.files[0]; + console.dir(f); + assert.equal(f.id, 'id_f347675c33d2e6b1c705c787fad4941a'); + assert.equal(f.filename, 'khmer_angkor.kmn'); + assert.equal(f.filePath, 'source/khmer_angkor.kmn'); + assert.equal(f.fileVersion, '1.3'); + assert.equal(f.fileType, '.kmn'); + assert.equal(f.details.name, 'Khmer Angkor'); + assert.equal(f.details.copyright, '© 2015-2022 SIL International'); + assert.equal(f.details.message, 'More than just a Khmer Unicode keyboard.'); + assert.isUndefined(f.details.version); + assert.lengthOf(f.childFiles, 1); + + f = project.files[1]; + assert.equal(f.id, 'id_8d4eb765f80c9f2b0f769cf4e4aaa456'); + assert.equal(f.filename, 'khmer_angkor.kps'); + assert.equal(f.filePath, 'source/khmer_angkor.kps'); + assert.equal(f.fileVersion, ''); + assert.equal(f.fileType, '.kps'); + assert.equal(f.details.name, 'Khmer Angkor'); + assert.isUndefined(f.details.message); + assert.equal(f.details.copyright, '© 2015-2022 SIL International'); + assert.isUndefined(f.details.version); + assert.lengthOf(f.childFiles, 18); + + f = project.files[0].childFiles[0]; + assert.equal(f.id, 'id_8a1efc7c4ab7cfece8aedd847679ca27'); + assert.equal(f.filename, 'khmer_angkor.ico'); + assert.equal(f.filePath, 'source/khmer_angkor.ico'); + assert.equal(f.fileVersion, ''); + assert.equal(f.fileType, '.ico'); + assert.isEmpty(f.details); + assert.lengthOf(f.childFiles, 0); + + f = project.files[1].childFiles[0]; + assert.equal(f.id, 'id_8dc195db32d1fd0514de0ad51fff5df0'); + assert.equal(f.filename, 'khmer_angkor.js'); + assert.equal(f.filePath, 'source/../build/khmer_angkor.js'); + assert.equal(f.fileVersion, ''); + assert.equal(f.fileType, '.js'); + assert.isEmpty(f.details); + assert.lengthOf(f.childFiles, 0); + }); +}); diff --git a/common/web/types/test/kvk/test-kvk-file.ts b/common/web/types/test/kvk/test-kvk-file.ts new file mode 100644 index 00000000000..7578169eea7 --- /dev/null +++ b/common/web/types/test/kvk/test-kvk-file.ts @@ -0,0 +1,15 @@ +import * as fs from 'fs'; +import 'mocha'; +import { makePathToFixture } from '../helpers/index.js'; +import KvkFileReader from "../../src/kvk/kvk-file-reader.js"; +import { verify_khmer_angkor } from './test-kvk-utils.js'; + +describe('kvk-file-reader', function () { + it('kvk-file-reader should read a valid file', function() { + const path = makePathToFixture('kvk', 'khmer_angkor.kvk'); + const input = fs.readFileSync(path); + const reader = new KvkFileReader(); + const vk = reader.read(input); + verify_khmer_angkor(vk); + }); +}); diff --git a/common/web/types/test/kvk/test-kvk-round-trip.ts b/common/web/types/test/kvk/test-kvk-round-trip.ts new file mode 100644 index 00000000000..487be68c1d6 --- /dev/null +++ b/common/web/types/test/kvk/test-kvk-round-trip.ts @@ -0,0 +1,87 @@ +import * as fs from 'fs'; +import 'mocha'; +import {assert} from 'chai'; +import Hexy from 'hexy'; +import gitDiff from 'git-diff'; +const { hexy } = Hexy; +import { loadKvksJsonSchema, makePathToFixture } from '../helpers/index.js'; +import KvksFileReader, { KVKSParseError } from "../../src/kvk/kvks-file-reader.js"; +import KvkFileReader from "../../src/kvk/kvk-file-reader.js"; +import KvkFileWriter from "../../src/kvk/kvk-file-writer.js"; +import KvksFileWriter from "../../src/kvk/kvks-file-writer.js"; + +/** + * + * @param actual input buffer + * @param expected expected buffer + * @returns + */ +function assertBufferMatch(actual: Buffer, expected: Buffer) { + const sourceHex = hexy(actual); + const expectedHex = hexy(expected); + const diff = gitDiff(expectedHex, sourceHex); + + assert(!diff, 'Buffers did not match:\n' + diff); + assert.equal(actual.length, expected.length, 'Buffer lengths are not the same'); + assert.deepEqual(actual, expected); // double check buffers: hexy can return '' on failure +} + +describe('kvk-file-reader', function () { + it('kvk-file-reader should round-trip with kvk-file-writer', function() { + const path = makePathToFixture('kvk', 'khmer_angkor.kvk'); + const input = fs.readFileSync(path); + const reader = new KvkFileReader(); + const vk = reader.read(input); + const writer = new KvkFileWriter(); + const output = writer.write(vk); + assertBufferMatch(input, Buffer.from(output)); + + // and check the 2nd generation also + const vk2 = reader.read(output); + assert.deepEqual(vk2, vk); + }); +}); + +describe('kvks-file-reader', function () { + it('kvks-file-reader should compile with kvk-file-writer', function() { + const inputPath = makePathToFixture('kvk', 'khmer_angkor.kvks'); + const compiledPath = makePathToFixture('kvk', 'khmer_angkor.kvk'); + const input = fs.readFileSync(inputPath); + const compiled = fs.readFileSync(compiledPath); + const reader = new KvksFileReader(); + const kvks = reader.read(input); + assert.doesNotThrow(() => { + reader.validate(kvks, loadKvksJsonSchema()); + }); + const errors: KVKSParseError[] = []; + const vk = reader.transform(kvks, errors); + assert.isEmpty(errors); + const writer = new KvkFileWriter(); + const output = writer.write(vk); + assertBufferMatch(Buffer.from(output), compiled); + }); + + describe('kvks-file-writer', function() { + it('kvks-file-writer should match what kvk-file-reader reads', function() { + const kvkIn = makePathToFixture('kvk', 'khmer_angkor.kvk'); + const kvkBuf = fs.readFileSync(kvkIn); + const kvkReader = new KvkFileReader(); + const kvksReader = new KvksFileReader(); + const vk = kvkReader.read(kvkBuf); + const kvksWriter = new KvksFileWriter(); + const kvksOut = kvksWriter.write(vk); + + // Now, re-read kvk from the kvks + const kvks = kvksReader.read(Buffer.from(kvksOut)); + assert.doesNotThrow(() => { + kvksReader.validate(kvks, loadKvksJsonSchema()); + }); + const errors: KVKSParseError[] = []; + const vk2 = kvksReader.transform(kvks, errors); + assert.isEmpty(errors); + + // make sure the binary is the same + assert.deepEqual(vk2, vk); + }); + }); +}); diff --git a/common/web/types/test/kvk/test-kvk-utils.ts b/common/web/types/test/kvk/test-kvk-utils.ts new file mode 100644 index 00000000000..34cd841cfd8 --- /dev/null +++ b/common/web/types/test/kvk/test-kvk-utils.ts @@ -0,0 +1,24 @@ +import 'mocha'; +import {assert} from 'chai'; +import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKeyFlags, VisualKeyboardShiftState } from "../../src/kvk/visual-keyboard.js"; +import { USVirtualKeyCodes } from '../../src/consts/virtual-key-constants.js'; + +export function verify_khmer_angkor(vk: VisualKeyboard) { + assert.equal(vk.header.flags, VisualKeyboardHeaderFlags.kvkhAltGr); + assert.equal(vk.header.associatedKeyboard, 'khmer_angkor'); + assert.equal(vk.header.ansiFont.name, 'Arial'); + assert.equal(vk.header.ansiFont.size, -12); + assert.equal(vk.header.ansiFont.color, 0xFF000008); + assert.equal(vk.header.unicodeFont.name, 'Khmer Busra Kbd'); + assert.equal(vk.header.unicodeFont.size, 16); + assert.equal(vk.header.unicodeFont.color, 0xFF000008); + assert.equal(vk.keys.length, 186); + assert.equal(vk.keys[0].flags, VisualKeyboardKeyFlags.kvkkUnicode); + assert.equal(vk.keys[0].vkey, USVirtualKeyCodes.K_B); + assert.equal(vk.keys[0].shift, VisualKeyboardShiftState.KVKS_RALT); + assert.equal(vk.keys[0].text, 'ឞ'); + assert.equal(vk.keys[185].flags, VisualKeyboardKeyFlags.kvkkUnicode); + assert.equal(vk.keys[185].vkey, USVirtualKeyCodes.K_COMMA); + assert.equal(vk.keys[185].shift, VisualKeyboardShiftState.KVKS_SHIFT); + assert.equal(vk.keys[185].text, ''); +} diff --git a/common/web/types/test/kvk/test-kvks-file.ts b/common/web/types/test/kvk/test-kvks-file.ts new file mode 100644 index 00000000000..e776a0a3613 --- /dev/null +++ b/common/web/types/test/kvk/test-kvks-file.ts @@ -0,0 +1,45 @@ +import * as fs from 'fs'; +import 'mocha'; +import { loadKvksJsonSchema, makePathToFixture } from '../helpers/index.js'; +import KvksFileReader, { KVKSParseError } from "../../src/kvk/kvks-file-reader.js"; +import KvksFileWriter from "../../src/kvk/kvks-file-writer.js"; +import { verify_khmer_angkor } from './test-kvk-utils.js'; +import { assert } from 'chai'; + +describe('kvks-file-reader', function() { + it('should read a valid file', function() { + const path = makePathToFixture('kvk', 'khmer_angkor.kvks'); + const input = fs.readFileSync(path); + + const reader = new KvksFileReader(); + const kvks = reader.read(input); + assert.doesNotThrow(() => { + reader.validate(kvks, loadKvksJsonSchema()); + }); + const errors: KVKSParseError[] = []; + const vk = reader.transform(kvks, errors); + assert.isEmpty(errors); + verify_khmer_angkor(vk); + }); +}); + +describe('kvks-file-writer', function() { + it('should write a valid file', function() { + const path = makePathToFixture('kvk', 'khmer_angkor.kvks'); + const input = fs.readFileSync(path); + + const reader = new KvksFileReader(); + const kvksExpected = reader.read(input); + const errors: KVKSParseError[] = []; + const vk = reader.transform(kvksExpected, errors); + assert.isEmpty(errors); + + const writer = new KvksFileWriter(); + const output = writer.write(vk); + + // We compare the (re)loaded data, because there may be + // minor, irrelevant formatting differences in the emitted xml + const kvks = reader.read(Buffer.from(output, 'utf8')); + assert.deepEqual(kvks, kvksExpected); + }); +}); diff --git a/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts new file mode 100644 index 00000000000..6a789c74936 --- /dev/null +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-testdata-reader.ts @@ -0,0 +1,51 @@ +import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { assert } from 'chai'; +import 'mocha'; +import { testTestdataReaderCases } from '../helpers/reader-callback-test.js'; + +describe('ldml keyboard xml reader tests', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + testTestdataReaderCases([ + { + subpath: 'test-fr.xml', + callback: (data, source) => { + assert.ok(source); + assert.ok(source.keyboardTest); + assert.equal(source.keyboardTest.conformsTo, constants.cldr_version_latest); + + assert.deepEqual(source.keyboardTest.info, { + keyboard: 'fr-t-k0-azerty.xml', + author: 'Team Keyboard', + name: 'fr-test' + }); + + assert.sameDeepMembers(source.keyboardTest.repertoire, [ + { + name: 'simple-repertoire', + chars: '[a b c d e \\u{22}]', + type: 'simple' + }, + { name: 'chars-repertoire', chars: '[á é ó]', type: 'gesture' } + ]); + + assert.equal(1, source.keyboardTest.tests?.length); + assert.equal('key-tests', source.keyboardTest.tests[0].name); + assert.equal(1, source.keyboardTest.tests[0].test?.length); + const test0 = source.keyboardTest.tests[0].test[0]; + assert.equal('key-test', test0.name); + assert.equal('abc\\u0022...', test0.startContext?.to); + assert.sameDeepOrderedMembers([ + { keystroke: { key: 's' } }, + { check: { result: 'abc\\u0022...s' } }, + { keystroke: { key: 't' } }, + { check: { result: 'abc\\u0022...st' } }, + { keystroke: { key: 'u' } }, + { check: { result: 'abc\\u0022...stu' } }, + { emit: { to: 'v' } }, + { check: { result: 'abc\\u0022...stuv' } }, + ], test0.actions); + }, + } + ]); +}); 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 new file mode 100644 index 00000000000..0fb15673842 --- /dev/null +++ b/common/web/types/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -0,0 +1,131 @@ +import { LKKey } from './../../src/ldml-keyboard/ldml-keyboard-xml'; +import 'mocha'; +import {assert} from 'chai'; +import { CommonTypesMessages } from '../../src/util/common-events.js'; +import { testReaderCases } from '../helpers/reader-callback-test.js'; + +function pluckKeysFromKeybag(keys: LKKey[], ids: string[]) { + return keys.filter(({id}) => ids.indexOf(id) !== -1); +} + +describe('ldml keyboard xml reader tests', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + testReaderCases([ + { + subpath: 'invalid-structure-per-dtd.xml', + errors: [CommonTypesMessages.Error_SchemaValidationError({ + instancePath: '/keyboard', + keyword: 'required', + message: `must have required property 'names'`, + params: 'missingProperty="names"', + })], + }, + { + subpath: 'invalid-conforms-to.xml', + errors: [CommonTypesMessages.Error_SchemaValidationError({ + instancePath: '/keyboard/conformsTo', + keyword: 'enum', + message: `must be equal to one of the allowed values`, + params: 'allowedValues="techpreview"', + })], + }, + { + subpath: 'import-minimal.xml', + callback: (data, source, subpath, callbacks) => { + assert.ok(source?.keyboard?.keys); + const k = pluckKeysFromKeybag(source?.keyboard?.keys.key, ['a', 'b', 'c']); + assert.sameDeepOrderedMembers(k, [ + {id: 'a', to: 'a'}, + {id: 'b', to: 'b'}, + {id: 'c', to: 'c'}, + ]); + }, + }, + { + subpath: 'import-minimal1.xml', + callback: (data, source, subpath, callbacks) => { + assert.ok(source?.keyboard?.keys); + const k = pluckKeysFromKeybag(source?.keyboard?.keys.key, ['a', 'b', 'c']); + assert.sameDeepOrderedMembers(k, [ + {id: 'a', to: 'a'}, + {id: 'b', to: 'b'}, + {id: 'c', to: 'c'}, + ]); + }, + }, + { + subpath: 'import-minimal2.xml', + callback: (data, source, subpath, callbacks) => { + assert.ok(source?.keyboard?.keys); + const k = pluckKeysFromKeybag(source?.keyboard?.keys.key, ['a', 'b', 'c']); + assert.sameDeepOrderedMembers(k, [ + {id: 'a', to: 'a'}, + {id: 'b', to: 'b'}, + {id: 'c', to: 'c'}, + {id: 'a', to: 'å'}, // overridden + ]); + }, + }, + { + subpath: 'import-symbols.xml', + callback: (data, source, subpath, callbacks) => { + assert.ok(source?.keyboard?.keys); + const k = pluckKeysFromKeybag(source?.keyboard?.keys.key, ['a', 'b', 'c', 'zz', 'hash', 'hyphen']); + assert.sameDeepOrderedMembers(k, [ + {id: 'a', to: 'a'}, // implied + {id: 'b', to: 'b'}, + {id: 'c', to: 'c'}, + {id: 'hash', to: '#'}, // imported symbols + {id: 'hyphen', to: '-'}, + {id: 'zz', to: 'zz'}, // new key + {id: 'hash', to: '##'}, // override + ]); + }, + }, + { + subpath: 'invalid-import-base.xml', + loadfail: true, + errors: [ + CommonTypesMessages.Error_ImportInvalidBase({ + base: 'SOME_INVALID_BASE', + path: 'B', + subtag: 'C' + }), + ], + }, + { + subpath: 'invalid-import-path.xml', + loadfail: true, + errors: [ + CommonTypesMessages.Error_ImportInvalidPath({ + base: null, + path: 'techpreview/too/many/slashes/leading/to/nothing-Zxxx-does-not-exist.xml', + subtag: null, + }), + ], + }, + { + subpath: 'invalid-import-readfail.xml', + loadfail: true, + errors: [ + CommonTypesMessages.Error_ImportReadFail({ + base: null, + path: 'techpreview/none-Zxxx-does-not-exist.xml', + subtag: null, + }), + ], + }, + { + subpath: 'invalid-import-wrongroot.xml', + loadfail: true, + errors: [ + CommonTypesMessages.Error_ImportWrongRoot({ + base: null, + path: 'techpreview/keys-Zyyy-punctuation.xml', + subtag: 'names', + }), + ], + }, + ]); +}); diff --git a/common/web/types/test/tsconfig.json b/common/web/types/test/tsconfig.json new file mode 100644 index 00000000000..9c97b463afd --- /dev/null +++ b/common/web/types/test/tsconfig.json @@ -0,0 +1,21 @@ +{ + "extends": "../../../../tsconfig.esm-base.json", + + "compilerOptions": { + "rootDir": ".", + "rootDirs": ["./", "../src/"], + "outDir": "../build/test", + "baseUrl": ".", + "strictNullChecks": false, // TODO: get rid of this as some point + "allowSyntheticDefaultImports": true // for ajv + }, + "include": [ + "**/test-*.ts", + "./helpers/*.ts", + ], + "references": [ + { "path": "../../keyman-version/tsconfig.esm.json" }, + { "path": "../../../../core/include/ldml/"}, + { "path": "../" }, + ] +} diff --git a/common/web/types/test/util/test-unescape.ts b/common/web/types/test/util/test-unescape.ts new file mode 100644 index 00000000000..5708046a80d --- /dev/null +++ b/common/web/types/test/util/test-unescape.ts @@ -0,0 +1,38 @@ +import 'mocha'; +import {assert} from 'chai'; +import {unescapeString, UnescapeError} from '../../src/util/util.js'; + +describe('test unescapeString()', function() { + it("should correctly handle multi strings", function() { + assert.equal(unescapeString('Sa\\u{0127 0127}a'), 'Saħħa'); + }); + + it("should pass through falsy strings", function() { + assert.equal(unescapeString(''), ''); + assert.equal(unescapeString(null), null); + }); + + it("should pass through non-escaped strings", function() { + assert.equal(unescapeString('abc'), 'abc'); + assert.equal(unescapeString('\\u{abc'), '\\u{abc'); + }); + + it("should correctly unescape strings", function() { + assert.equal(unescapeString('\\u{0127}'), 'ħ'); + assert.equal(unescapeString('\\u{0127}==\\u{0127}'), 'ħ==ħ'); + assert.equal(unescapeString('Q\\u{0127}'), 'Qħ'); + }); + + it("should correctly handle 1..6 char escapes", function() { + assert.equal(unescapeString('\\u{9}'), '\u{0009}'); // TAB + assert.equal(unescapeString('\\u{4a}'), '\u{004a}'); // J + assert.equal(unescapeString('\\u{3c8}'), '\u{03c8}'); // ψ + assert.equal(unescapeString('\\u{304B}'), '\u{304b}'); // か + assert.equal(unescapeString('\\u{1e109}'), '\u{1e109}'); // 𞄉 + assert.equal(unescapeString('\\u{10fff0}'), '\u{10fff0}'); // Plane 16 Private Use + }); + + it("should throw UnescapeError on invalid escapes", function() { + assert.throws(() => unescapeString('\\u{110000}'), UnescapeError); + }); +}); diff --git a/common/web/types/tsconfig.json b/common/web/types/tsconfig.json new file mode 100644 index 00000000000..a32b8e1ed7f --- /dev/null +++ b/common/web/types/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tsconfig.esm-base.json", + + "compilerOptions": { + "outDir": "build/src/", + "rootDir": "src/", + "baseUrl": ".", + "allowSyntheticDefaultImports": true // for ajv + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { "path": "../keyman-version/tsconfig.esm.json" }, + { "path": "../../../core/include/ldml/tsconfig.json"}, + ] +} diff --git a/common/windows/cpp/include/legacy_kmx_file.h b/common/windows/cpp/include/legacy_kmx_file.h index 8e121be13f7..eddbf233411 100644 --- a/common/windows/cpp/include/legacy_kmx_file.h +++ b/common/windows/cpp/include/legacy_kmx_file.h @@ -1,3 +1,5 @@ +// TODO: merge and replace with kmx_file.h from core + /* Name: legacy_kmx_file Copyright: Copyright (C) SIL International. @@ -71,9 +73,9 @@ #define VERSION_100 0x00000A00 #define VERSION_140 0x00000E00 #define VERSION_150 0x00000F00 - +#define VERSION_160 0x00001000 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_150 +#define VERSION_MAX VERSION_160 /* Special flag for WM_CHAR/WM_KEY???/WM_SYSKEY???: says that key has been @@ -283,6 +285,9 @@ #define KF_LOGICALLAYOUT 0x0008 #define KF_AUTOMATICVERSION 0x0010 +// 16.0: Support for LDML Keyboards in KMXPlus file format +#define KF_KMXPLUS 0x0020 + #define HK_ALT 0x00010000 #define HK_CTRL 0x00020000 #define HK_SHIFT 0x00040000 @@ -365,7 +370,7 @@ struct COMP_KEYBOARD { DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400 - DWORD dwCheckSum; // 0008 As stored in keyboard + DWORD dwCheckSum; // 0008 As stored in keyboard. DEPRECATED as of 16.0 DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts DWORD IsRegistered; // 0010 DWORD version; // 0014 keyboard version diff --git a/common/windows/cpp/include/legacy_kmx_memory.h b/common/windows/cpp/include/legacy_kmx_memory.h index 0dd681c6a32..771a4d57ca9 100644 --- a/common/windows/cpp/include/legacy_kmx_memory.h +++ b/common/windows/cpp/include/legacy_kmx_memory.h @@ -34,7 +34,7 @@ typedef struct tagKEYBOARD DWORD dwFileVersion; // Version of the file - Keyman 4.0 is 0x0400 - DWORD dwCheckSum; // As stored in keyboard + DWORD dwCheckSum; // As stored in keyboard. DEPRECATED as of 16.0 DWORD xxkbdlayout; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts DWORD IsRegistered; // layout id, from same registry key DWORD version; // keyboard version diff --git a/common/windows/delphi/general/CRC32.pas b/common/windows/delphi/general/CRC32.pas deleted file mode 100644 index a7d4bd6d915..00000000000 --- a/common/windows/delphi/general/CRC32.pas +++ /dev/null @@ -1,129 +0,0 @@ -(* - Name: CRC32 - Copyright: Copyright (C) SIL International. - Documentation: - Description: - Create Date: 17 Aug 2012 - - Modified Date: 17 Aug 2012 - Authors: mcdurdin - Related Files: - Dependencies: - - Bugs: - Todo: - Notes: - History: 17 Aug 2012 - mcdurdin - I3310 - V9.0 - Unicode in Delphi fixes -*) -unit CRC32; - -interface - -uses - System.Classes, - System.SysUtils, - Winapi.Windows; - -function CalculateBufferCRC(Count: LongWord; p: PByte): LongWord; // I3310 -function CalculateStringCRC(const s: ansistring): LongWord; // I3310 -function Dehash(s: PByteArray; sz: Integer): string; -procedure Hash(s: string; sz: Integer; outbuf: PByteArray); - -function GetCRC32Xor(const filename: string; x: LongWord): LongWord; - -implementation - -{ - * Instead of performing a straightforward calculation of the 32 bit - * CRC using a series of logical operations, this program uses the - * faster table lookup method. This routine is called once when the - * program starts up to build the table which will be used later - * when calculating the CRC values. -} - -var - CRCTable: array[0..255] of LongWord; - TableBuilt: Boolean = False; - -const - CRC32_POLYNOMIAL: LongWord = $EDB88320; - -procedure BuildCRCTable; -var - i, j: Integer; - crc: LongWord; -begin - if not TableBuilt then - for i := 0 to 255 do - begin - crc := i; - - for j := 8 downto 1 do - if (crc and 1) = 1 then crc := (crc shr 1) xor CRC32_POLYNOMIAL else crc := crc shr 1; - - CRCTable[i] := crc; - end; -end; - - -{ - * This routine calculates the CRC for a block of data using the - * table lookup method. It accepts an original value for the crc, - * and returns the updated value. -} - -function CalculateBufferCRC(Count: LongWord; p: PByte): LongWord; // I3310 -var - temp1, temp2, crc: LongWord; -begin - crc := $FFFFFFFF; - - while Count <> 0 do - begin - temp1 := (crc shr 8) and $00FFFFFF; - temp2 := CRCTable[(Integer(crc) xor Integer(p^)) and $ff]; - crc := temp1 xor temp2; - Inc(p); - Dec(Count); - end; - - Result := crc; -end; - -function CalculateStringCRC(const s: ansistring): LongWord; // I3310 -begin - Result := CalculateBufferCRC(Length(s), PByte(PAnsiChar(s))); // I3310 -end; - -procedure Hash(s: string; sz: Integer; outbuf: PByteArray); -var - i: Integer; -begin - for i := 1 to sz do outbuf[i-1] := Ord(s[i]) xor $6D; -end; - -function Dehash(s: PByteArray; sz: Integer): string; -var - i: Integer; -begin - Result := ''; - for i := 0 to sz-1 do - Result := Result + Chr(s[i] xor $6D); -end; - - -function GetCRC32Xor(const filename: string; x: LongWord): LongWord; -begin - with TMemoryStream.Create do - try - LoadFromFile(filename); - Result := CalculateBufferCRC(Size, Memory) xor x; - finally - Free; - end; -end; - -initialization - BuildCRCTable; -end. - diff --git a/common/windows/delphi/keyboards/kmxfile.pas b/common/windows/delphi/keyboards/kmxfile.pas index fe7311d879d..1c658c22077 100644 --- a/common/windows/delphi/keyboards/kmxfile.pas +++ b/common/windows/delphi/keyboards/kmxfile.pas @@ -114,7 +114,7 @@ function SUBLANGID(n: DWORD): WORD; TKeyboardFileHeader = packed record dwIdentifier: Dword; // Keyman compiled keyboard id dwFileVersion: Dword; // 0x0400 or 0x0500 - dwCheckSum: Dword; + dwCheckSum: Dword; // As stored in keyboard. DEPRECATED as of 16.0 KeyboardID: DWord; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts IsRegistered: DWord; // was layout id; version: DWord; // keyboard file version with VERSION keyword @@ -182,7 +182,6 @@ function HotkeyAsString(hotkey: DWord): string; implementation uses - crc32, KeyNames, utildir, utilfiletypes; @@ -195,8 +194,10 @@ implementation KF_CAPSONONLY = $0002; KF_CAPSALWAYSOFF = $0004; KF_LOGICALLAYOUT = $0008; + KF_AUTOMATICVERSION = $0010; - + // 16.0: Support for LDML Keyboards in KMXPlus file format + KF_KMXPLUS = $0020; function GetSystemStore(Memory: PByte; SystemID: DWord; var Buffer: WideString): Boolean; // I3310 var @@ -228,7 +229,6 @@ function GetSystemStore(Memory: PByte; SystemID: DWord; var Buffer: WideString): procedure GetKeyboardInfo(const FileName: string; FReturnDump: Boolean; var ki: TKeyboardInfo; FReturnBitmap: Boolean = False); var kfh: TKeyboardFileHeader; - cs: DWord; mem: TMemoryStream; ver: WideString; shver: string; @@ -251,15 +251,6 @@ procedure GetKeyboardInfo(const FileName: string; FReturnDump: Boolean; var ki: Position := 0; Read(kfh, SizeOf(TKeyboardFileHeader)); - cs := kfh.dwCheckSum; - kfh.dwCheckSum := 0; - - Position := 0; - Write(kfh, SizeOf(TKeyboardFileHeader)); - - if CalculateBufferCRC(Size, Memory) <> cs then - raise EKMXError.CreateFmt(EKMX_InvalidKeyboardFile, 'The keyboard file %0:s is invalid', [ExtractFileName(FileName)]); - if kfh.dwIdentifier <> FILEID_COMPILED then raise EKMXError.CreateFmt(EKMX_InvalidKeyboardFile, 'The keyboard file %0:s is invalid', [ExtractFileName(FileName)]); diff --git a/common/windows/delphi/keyboards/kmxfileconsts.pas b/common/windows/delphi/keyboards/kmxfileconsts.pas index d302528847d..e08feb65641 100644 --- a/common/windows/delphi/keyboards/kmxfileconsts.pas +++ b/common/windows/delphi/keyboards/kmxfileconsts.pas @@ -98,9 +98,10 @@ interface VERSION_100 = $00000A00; VERSION_140 = $00000E00; VERSION_150 = $00000F00; + VERSION_160 = $00001000; VERSION_MIN = VERSION_50; - VERSION_MAX = VERSION_150; + VERSION_MAX = VERSION_160; VERSION_MASK_MINOR = $00FF; VERSION_MASK_MAJOR = $FF00; diff --git a/core/.build-builder b/core/.build-builder new file mode 100644 index 00000000000..faf4c870a92 --- /dev/null +++ b/core/.build-builder @@ -0,0 +1,4 @@ +The presence of this file tells CI to use the new builder_ style parameters for build.sh. + +Once all branches for 16.0+ are updated to merge the chore/core/7247-use-builder-for-build-sh branch, then we +can remove this file and the corresponding bash test branches in CI. diff --git a/core/build.bat b/core/build.bat index c720c94bffc..5dd219e9f27 100644 --- a/core/build.bat +++ b/core/build.bat @@ -15,14 +15,13 @@ goto help rem ---------------------------------- :help -echo Usage: %0 x86^|x64^|all debug^|release [build] [tests] +echo Usage: %0 x86^|x64^|all debug^|release [configure] [build] [test] [additional params for meson/ninja] echo or -echo Usage: %0 x86^|x64^|all -c +echo Usage: %0 x86^|x64 -c echo -c will leave your environment configured for Visual Studio for selected platform. -echo -c can be used only with x86 and x64 options echo. echo Otherwise, %0 is intended to be used by build.sh, not directly. -echo At least one of 'build' or 'tests' is required. +echo At least one of 'configure', 'build', or 'test' is required. goto :eof rem ---------------------------------- @@ -31,10 +30,10 @@ rem ---------------------------------- setlocal cd %KEYMAN_ROOT%\core -cmd /c build.bat x86 %2 %3 %4 %5 || exit !errorlevel! +cmd /c build.bat x86 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! cd %KEYMAN_ROOT%\core -cmd /c build.bat x64 %2 %3 %4 %5 || exit !errorlevel! +cmd /c build.bat x64 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! goto :eof @@ -42,15 +41,16 @@ rem ---------------------------------- :build -if "%2"=="-c" goto :setup - set ARCH=%1 +shift + +if "%1"=="-c" goto :setup echo === Locating Visual Studio === rem From https://github.com/microsoft/vswhere -for /f "usebackq tokens=*" %%i in (`..\resources\build\vswhere -latest -requires Microsoft.Component.MSBuild -find **\vcvarsall.bat`) do ( - set VCVARSALL="%%i" +for /f "usebackq delims=#" %%a in (`"%programfiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -property installationPath`) do ( + set VsDevCmd_Path=%%a\Common7\Tools\VsDevCmd.bat ) if errorlevel 1 ( @@ -58,41 +58,50 @@ if errorlevel 1 ( exit /b !errorlevel! ) -if not exist "!VCVARSALL!" ( - echo Could not find vcvarsall.bat [!VCVARSALL!] +if not exist "!VsDevCmd_Path!" ( + echo Could not find vsdevcmd.bat [!VsDevCmd_Path!] exit /b 1 ) echo === Configuring VC++ === -call !VCVARSALL! !ARCH! || exit !errorlevel! +set VSCMD_SKIP_SENDTELEMETRY=1 +call "!VsDevCmd_Path!" -arch=!ARCH! -no_logo -startdir=none || exit !errorlevel! cd %KEYMAN_ROOT%\core -set BUILDTYPE=%2 +set BUILDTYPE=%1 +shift set STATIC_LIBRARY=--default-library both -if "%3" == "build" ( - echo === Calling meson build for Windows !ARCH! !BUILDTYPE! === +set COMMAND=%1 +shift + +if "!COMMAND!" == "configure" ( + echo === Configuring Keyman Core for Windows !ARCH! !BUILDTYPE! === if exist build\!ARCH!\!BUILDTYPE! rd /s/q build\!ARCH!\!BUILDTYPE! - meson build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! --werror || exit !errorlevel! + if "%1" == "--no-tests" ( + meson setup build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! -Dkeyman_core_tests=false --werror %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! + ) else ( + meson setup build\!ARCH!\!BUILDTYPE! !STATIC_LIBRARY! --buildtype !BUILDTYPE! --werror %1 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! + ) + shift +) +if "!COMMAND!" == "build" ( echo === Building Keyman Core for Windows !ARCH! !BUILDTYPE! === cd build\!ARCH!\!BUILDTYPE! || exit !errorlevel! - - ninja || exit !errorlevel! + ninja %1 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! cd ..\..\.. - shift ) -if "%3" == "tests" ( - cd build\!ARCH!/!BUILDTYPE! || exit !errorlevel! - - echo === Running tests for Windows !ARCH! !BUILDTYPE! === - meson test --print-errorlogs || exit !errorlevel! - +if "!COMMAND!" == "test" ( + echo === Testing Keyman Core for Windows !ARCH! !BUILDTYPE! === + cd build\!ARCH!\!BUILDTYPE! || exit !errorlevel! + meson test --print-errorlogs %1 %2 %3 %4 %5 %6 %7 %8 %9 || exit !errorlevel! cd ..\..\.. + shift ) goto :eof @@ -105,10 +114,12 @@ rem Standalone build, so we'll make the environment available to the caller rem Also setup rem Note: Visual Studio 2022 doesn't provide vcvarsall.bat, so we'll have to find a different solution endlocal -for /f "usebackq tokens=*" %%i in (`..\resources\build\vswhere -version [15^,17^) -latest -requires Microsoft.Component.MSBuild -find **\vcvarsall.bat`) do ( - set VCVARSALL="%%i" +for /f "usebackq delims=#" %%a in (`"%programfiles(x86)%\Microsoft Visual Studio\Installer\vswhere" -latest -property installationPath`) do ( + set VsDevCmd_Path=%%a\Common7\Tools\VsDevCmd.bat ) -%VCVARSALL% %1 + +set VSCMD_SKIP_SENDTELEMETRY=1 +"!VsDevCmd_Path!" -arch=!ARCH! -no_logo -startdir=none goto :eof rem ---------------------------------- diff --git a/core/build.sh b/core/build.sh index 09008bfee94..b746065b482 100755 --- a/core/build.sh +++ b/core/build.sh @@ -11,293 +11,119 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" +. "$THIS_SCRIPT_PATH/commands.inc.sh" -display_usage() { - echo "usage: build.sh [build options] [targets] [-- options to pass to c++ configure]" - echo - echo "Build options:" - echo " --debug, -d Debug build" - echo " --target, -t Target path (linux,macos only, default build/)" - echo " --platform, -p Platform to build (wasm or native, default native)" - echo - echo "Targets (all except install if not specified):" - echo " clean Clean target path" - echo " configure Configure libraries (linux,macos only)" - echo " build Build all libraries" - echo " build-cpp Build c++ libraries" - echo " tests Run all tests" - echo " tests-cpp Run c++ tests" - echo " install Install all libraries" - echo " install-cpp Install c++ libraries" - echo " uninstall Uninstall all libraries" - echo " uninstall-cpp Uninstall c++ libraries" - echo - echo "C++ libraries will be in: TARGETPATH///src" - echo "WASM libraries will be in: TARGETPATH/wasm//src" - echo "On Windows, will be 'x86' or 'x64'; elsewhere it is 'arch'" - exit 0 -} +cd "$THIS_SCRIPT_PATH" -MESON_TARGET=release -HAS_TARGET=false -CLEAN=false -CONFIGURE=false -BUILD_CPP=false -TESTS_CPP=false -INSTALL_CPP=false -UNINSTALL_CPP=false -QUIET=false -TARGET_PATH="$THIS_SCRIPT_PATH/build" -ADDITIONAL_ARGS= -PLATFORM=native +################################ Main script ################################ -# Parse args -shopt -s nocasematch +cleanup_visual_studio_path -while [[ $# -gt 0 ]] ; do - key="$1" - case $key in - --debug|-d) - MESON_TARGET=debug - ;; - --help|-\?) - display_usage - ;; - --target|-t) - TARGET_PATH=$(readlink -f "$2") - shift - ;; - --platform|-p) - PLATFORM="$2" - shift - ;; - configure) - HAS_TARGET=true - CONFIGURE=true - ;; - clean) - HAS_TARGET=true - CLEAN=true - ;; - build) - HAS_TARGET=true - BUILD_CPP=true - ;; - *-rust) - echo "$key: Rust was removed in " - ;; - build-cpp) - HAS_TARGET=true - BUILD_CPP=true - ;; - tests) - HAS_TARGET=true - TESTS_CPP=true - ;; - tests-cpp) - HAS_TARGET=true - TESTS_CPP=true - ;; - install) - HAS_TARGET=true - INSTALL_CPP=true - ;; - install-cpp) - HAS_TARGET=true - INSTALL_CPP=true - ;; - uninstall) - HAS_TARGET=true - # ninja records the files it installs, so unless we install first we don't know - # what to uninstall. Installing will overwrite the existing files, if we then - # then uninstall the files get removed - unless previously we had additional files. - INSTALL_CPP=true - UNINSTALL_CPP=true - ;; - uninstall-cpp) - HAS_TARGET=true - INSTALL_CPP=true - UNINSTALL_CPP=true - ;; - --) - shift - ADDITIONAL_ARGS=$@ - break - ;; - *) - builder_die "Invalid parameters. Use --help for help" - esac - shift -done - -if ! $HAS_TARGET; then - if [ ! -f "$TARGET_PATH" ]; then - CONFIGURE=true - fi - BUILD_CPP=true - TESTS_CPP=true -fi +MESON_LOW_VERSION=false -if [[ $PLATFORM == wasm ]]; then - MESON_PATH="$TARGET_PATH/wasm/$MESON_TARGET" -else - MESON_PATH="$TARGET_PATH/arch/$MESON_TARGET" +if [[ `meson --version` < 0.54 ]]; then + MESON_LOW_VERSION=true fi -displayInfo "" \ - "VERSION: $VERSION" \ - "TIER: $TIER" \ - "PLATFORM: $PLATFORM" \ - "CONFIGURE: $CONFIGURE" \ - "CLEAN: $CLEAN" \ - "BUILD_CPP: $BUILD_CPP" \ - "TESTS_CPP: $TESTS_CPP" \ - "INSTALL_CPP: $INSTALL_CPP" \ - "UNINSTALL_CPP: $UNINSTALL_CPP" \ - "MESON_TARGET: $MESON_TARGET" \ - "TARGET_PATH: $TARGET_PATH" \ - "" - -clean() { - rm -rf "${TARGET_PATH:?}/" -} - -path_remove() { - # Delete path by parts so we can never accidentally remove sub paths - PATH=${PATH//":$1:"/":"} # delete any instances in the middle - PATH=${PATH/#"$1:"/} # delete any instance at the beginning - PATH=${PATH/%":$1"/} # delete any instance in the at the end -} - -build_windows() { - # Build targets for Windows - - # Build the meson targets, both x86 and x64 also - # We need to use a batch file here so we can get - # the Visual Studio build environment with vcvarsall.bat - # TODO: if PATH is the only variable required, let's try and - # eliminate this difference in the build process - - if $BUILD_CPP; then - if $TESTS_CPP; then - builder_heading "======= Building and Testing C++ library for Windows (x86, x64) =======" - cmd //C build.bat all $MESON_TARGET build tests - else - builder_heading "======= Building C++ library for Windows (x86, x64) =======" - cmd //C build.bat all $MESON_TARGET build - fi - elif $TESTS_CPP; then - builder_heading "======= Testing C++ library for Windows (x86, x64) =======" - cmd //C build.bat all $MESON_TARGET tests - fi -} - -build_standard() { - local BUILD_PLATFORM="$1" - local ARCH="$2" - local RUSTARCH=${3:-} - # RUSTARCH is not currently used. - if [ $# -gt 3 ]; then - shift 3 - local STANDARD_MESON_ARGS="$*" - else - local STANDARD_MESON_ARGS= - fi - - # Build meson targets - if $CONFIGURE; then - builder_heading "======= Configuring C++ library for $BUILD_PLATFORM =======" - pushd "$THIS_SCRIPT_PATH" > /dev/null - meson setup "$MESON_PATH" --werror --buildtype $MESON_TARGET $STANDARD_MESON_ARGS $ADDITIONAL_ARGS - popd > /dev/null - fi - - if $BUILD_CPP; then - builder_heading "======= Building C++ library for $BUILD_PLATFORM =======" - pushd "$MESON_PATH" > /dev/null - ninja - popd > /dev/null - fi - - if $TESTS_CPP; then - builder_heading "======= Testing C++ library for $BUILD_PLATFORM =======" - pushd "$MESON_PATH" > /dev/null - meson test --print-errorlogs - popd > /dev/null - fi - - if $INSTALL_CPP; then - builder_heading "======= Installing C++ libraries for $BUILD_PLATFORM =======" - pushd "$MESON_PATH" > /dev/null - ninja install - popd > /dev/null - fi - - if $UNINSTALL_CPP; then - builder_heading "======= Uninstalling C++ libraries for $BUILD_PLATFORM =======" - pushd "$MESON_PATH" > /dev/null - ninja uninstall - popd > /dev/null - fi -} - -# -# We don't want to rely on emcc being on the path, because Emscripten puts far -# too many things onto the path (in particular for us, node). -# -# The following comment suggests that we don't need emcc on the path. -# https://github.com/emscripten-core/emscripten/issues/4848#issuecomment-1097357775 # -# So we try and locate emcc in common locations ourselves. The search pattern -# is: +# Restrict available targets to those that can be built on the current system # -# 1. Look for $EMSCRIPTEN_BASE (our primary emscripten variable), which should -# point to the folder that emcc is located in -# 2. Look for $EMCC which should point to the emcc executable -# 3. Look for emcc on the path -# -locate_emscripten() { - if [[ -z ${EMSCRIPTEN_BASE+x} ]]; then - if [[ -z ${EMCC+x} ]]; then - local EMCC=`which emcc` - [[ -z $EMCC ]] && builder_die "locate_emscripten: Could not locate emscripten (emcc) on the path or with \$EMCC or \$EMSCRIPTEN_BASE" - fi - [[ -x $EMCC ]] || builder_die "locate_emscripten: Variable EMCC ($EMCC) does not point to a valid executable emcc" - EMSCRIPTEN_BASE="$(dirname "$EMCC")" - fi - - [[ -x ${EMSCRIPTEN_BASE}/emcc ]] || builder_die "locate_emscripten: Variable EMSCRIPTEN_BASE ($EMSCRIPTEN_BASE) does not point to emcc's folder" -} -build_meson_cross_file_for_wasm() { - if [[ $BUILDER_OS == win ]]; then - local R=$(cygpath -w $(echo $EMSCRIPTEN_BASE) | sed 's_\\_\\\\_g') - else - local R=$(echo $EMSCRIPTEN_BASE | sed 's_/_\\/_g') - fi - sed -e "s/\$EMSCRIPTEN_BASE/$R/g" wasm.build.$BUILDER_OS.in > wasm.build -} +archtargets=(":wasm WASM build") + +case $BUILDER_OS in + win) + archtargets+=( + ":x86 32-bit Windows (x86) build" + ":x64 64-bit Windows (x64) build" + ) + ;; + mac|linux) + archtargets+=( + ":arch Linux or mac build -- current architecture" + ) + ;; +esac + +# TODO: consider using "linux" and "mac" instead of "arch"? +# ":linux Build for current Linux architecture" +# ":mac Build for current macOS architecture" + +builder_describe \ +"Build Keyman Core + +Libraries will be built in 'build///src'. + * : 'debug' or 'release' (see --debug flag) + * All parameters after '--' are passed to meson or ninja +" \ + "@/common/tools/hextobin" \ + "@/common/web/keyman-version" \ + "@/developer/src/kmc" \ + "clean" \ + "configure" \ + "build" \ + "test" \ + "install install libraries to current system" \ + "uninstall uninstall libraries from current system" \ + "${archtargets[@]}" \ + "--debug,-d configuration is 'debug', not 'release'" \ + "--no-tests do not configure tests (used by other projects)" \ + "--target-path=opt_target_path override for build/ target path" \ + "--test=opt_tests,-t test[s] to run (space separated)" + +builder_parse "$@" -### +# +# meson forces us to configure tests, including building compilers, even +# if we don't plan to run them, for example when doing a dependency build +# in CI +# +MESON_OPTION_keyman_core_tests= +BUILD_BAT_keyman_core_tests= + +if builder_is_dep_build || builder_has_option --no-tests; then + MESON_OPTION_keyman_core_tests="-Dkeyman_core_tests=false" + BUILD_BAT_keyman_core_tests=--no-tests + builder_remove_dep /common/tools/hextobin + builder_remove_dep /common/web/keyman-version + builder_remove_dep /developer/src/kmc +fi -if $CLEAN; then - clean +if builder_has_option --debug; then + CONFIGURATION=debug +else + CONFIGURATION=release fi -if [[ $PLATFORM == native ]]; then - case $BUILDER_OS in - linux) - build_standard linux arch - ;; - mac) - build_standard mac arch - ;; - win) - build_windows - ;; - esac +builder_describe_outputs \ + configure:x86 build/x86/$CONFIGURATION/build.ninja \ + configure:x64 build/x64/$CONFIGURATION/build.ninja \ + configure:arch build/arch/$CONFIGURATION/build.ninja \ + configure:wasm build/wasm/$CONFIGURATION/build.ninja \ + build:x86 build/x86/$CONFIGURATION/src/libkmnkbp0.a \ + build:x64 build/x64/$CONFIGURATION/src/libkmnkbp0.a \ + build:arch build/arch/$CONFIGURATION/src/libkmnkbp0.a \ + build:wasm build/wasm/$CONFIGURATION/src/libkmnkbp0.a + +# Target path is used by Linux build, e.g. --target-path keyboardprocessor +if builder_has_option --target-path; then + TARGET_PATH="$opt_target_path" else - locate_emscripten - build_meson_cross_file_for_wasm - build_standard wasm wasm wasm32-unknown-unknown --cross-file wasm.defs.build --cross-file wasm.build --default-library static + TARGET_PATH="$KEYMAN_ROOT/core/build" fi + +# Iterate through all possible targets; note that targets that cannot be built +# on the current platform have already been excluded through the archtargets +# settings above +targets=(wasm x86 x64 arch) + +for target in "${targets[@]}"; do + MESON_PATH="$TARGET_PATH/$target/$CONFIGURATION" + + do_clean $target + do_configure $target + do_build $target + do_test $target + do_install $target + do_uninstall $target +done diff --git a/core/commands.inc.sh b/core/commands.inc.sh new file mode 100644 index 00000000000..e915b34cc9e --- /dev/null +++ b/core/commands.inc.sh @@ -0,0 +1,169 @@ +#!/usr/bin/env bash + +# ---------------------------------------------------------------------------- +# clean +# ---------------------------------------------------------------------------- + +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" + builder_finish_action success clean:$target +} + +# ---------------------------------------------------------------------------- +# configure +# ---------------------------------------------------------------------------- + +do_configure() { + local target=$1 + builder_start_action configure:$target || return 0 + + local STANDARD_MESON_ARGS="$MESON_OPTION_keyman_core_tests" + + builder_heading "======= Configuring $target =======" + + if [[ $target == wasm ]]; then + # do_configure_wasm + locate_emscripten + build_meson_cross_file_for_wasm + STANDARD_MESON_ARGS="$STANDARD_MESON_ARGS --cross-file wasm.defs.build --cross-file wasm.build --default-library static" + fi + + if [[ $target =~ ^(x86|x64)$ ]]; then + cmd //C build.bat $target $CONFIGURATION configure $BUILD_BAT_keyman_core_tests "${builder_extra_params[@]}" + else + pushd "$THIS_SCRIPT_PATH" > /dev/null + # Additional arguments are used by Linux build, e.g. -Dprefix=${INSTALLDIR} + meson setup "$MESON_PATH" --werror --buildtype $CONFIGURATION $STANDARD_MESON_ARGS "${builder_extra_params[@]}" + popd > /dev/null + fi + + builder_finish_action success configure:$target +} + +# ---------------------------------------------------------------------------- +# build +# ---------------------------------------------------------------------------- + +do_build() { + local target=$1 + builder_start_action build:$target || return 0 + if [[ $target =~ ^(x86|x64)$ ]]; then + cmd //C build.bat $target $CONFIGURATION build "${builder_extra_params[@]}" + elif $MESON_LOW_VERSION; then + pushd "$MESON_PATH" > /dev/null + ninja + popd + else + meson compile -C "$MESON_PATH" + fi + builder_finish_action success build:$target +} + +# ---------------------------------------------------------------------------- +# test +# ---------------------------------------------------------------------------- + +do_test() { + local target=$1 + builder_start_action test:$target || return 0 + if [[ $target =~ ^(x86|x64)$ ]]; then + cmd //C build.bat $target $CONFIGURATION test "${builder_extra_params[@]}" + else + meson test -C "$MESON_PATH" "${builder_extra_params[@]}" + fi + builder_finish_action success test:$target +} + +# ---------------------------------------------------------------------------- +# install and uninstall +# ---------------------------------------------------------------------------- + +do_install() { + local target=$1 + builder_start_action install:$target || return 0 + if $MESON_LOW_VERSION; then + pushd "$MESON_PATH" > /dev/null + ninja install + popd > /dev/null + else + meson install -C "$MESON_PATH" + fi + builder_finish_action success install:$target +} + +do_uninstall() { + local target=$1 + builder_start_action uninstall:$target || return 0 + pushd "$MESON_PATH" > /dev/null + # Note: there is no meson uninstall command, which means + # this probably won't work on Windows + ninja uninstall + popd > /dev/null + builder_finish_action success uninstall:$target +} + +# ---------------------------------------------------------------------------- +# utility functions +# ---------------------------------------------------------------------------- + +# +# We don't want to rely on emcc being on the path, because Emscripten puts far +# too many things onto the path (in particular for us, node). +# +# The following comment suggests that we don't need emcc on the path. +# https://github.com/emscripten-core/emscripten/issues/4848#issuecomment-1097357775 +# +# So we try and locate emcc in common locations ourselves. The search pattern +# is: +# +# 1. Look for $EMSCRIPTEN_BASE (our primary emscripten variable), which should +# point to the folder that emcc is located in +# 2. Look for $EMCC which should point to the emcc executable +# 3. Look for emcc on the path +# +locate_emscripten() { + if [[ -z ${EMSCRIPTEN_BASE+x} ]]; then + if [[ -z ${EMCC+x} ]]; then + local EMCC=`which emcc` + [[ -z $EMCC ]] && builder_die "locate_emscripten: Could not locate emscripten (emcc) on the path or with \$EMCC or \$EMSCRIPTEN_BASE" + fi + [[ -x $EMCC ]] || builder_die "locate_emscripten: Variable EMCC ($EMCC) does not point to a valid executable emcc" + EMSCRIPTEN_BASE="$(dirname "$EMCC")" + fi + + [[ -x ${EMSCRIPTEN_BASE}/emcc ]] || builder_die "locate_emscripten: Variable EMSCRIPTEN_BASE ($EMSCRIPTEN_BASE) does not point to emcc's folder" +} + +build_meson_cross_file_for_wasm() { + if [ $BUILDER_OS == win ]; then + local R=$(cygpath -w $(echo $EMSCRIPTEN_BASE) | sed 's_\\_\\\\_g') + else + local R=$(echo $EMSCRIPTEN_BASE | sed 's_/_\\/_g') + fi + sed -e "s/\$EMSCRIPTEN_BASE/$R/g" wasm.build.$BUILDER_OS.in > wasm.build +} + +# +# Remove Visual Studio from the path so that meson goes looking for it rather than +# assuming that it's all available. If we don't do this, we get an error with link.exe: +# +# meson.build:8:0: ERROR: Found GNU link.exe instead of MSVC link.exe in C:\Program Files\Git\usr\bin\link.EXE. +# This link.exe is not a linker. +# You may need to reorder entries to your %PATH% variable to resolve this. +# + +cleanup_visual_studio_path() { + local _split_path _new_path="" + IFS=':' read -ra _split_path <<<"$PATH" + for p in "${_split_path[@]}"; do + if ! [[ $p =~ Visual\ Studio ]]; then + _new_path="$_new_path:$p" + fi + done + echo + PATH="${_new_path:1}" + unset VSINSTALLDIR +} diff --git a/core/include/keyman/keyboardprocessor.h b/core/include/keyman/keyboardprocessor.h index 6ea04703506..3513cdede2e 100644 --- a/core/include/keyman/keyboardprocessor.h +++ b/core/include/keyman/keyboardprocessor.h @@ -1041,6 +1041,13 @@ enum km_kbp_tech_value { KM_KBP_TECH_LDML = 1 << 2 }; +/** + * Bit flags to be used with the event_flags parameter of km_kbp_process_event + */ +enum km_kbp_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 +}; /* ``` @@ -1081,6 +1088,7 @@ state is passed. - __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 ```c */ @@ -1089,7 +1097,8 @@ km_kbp_status km_kbp_process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state, - uint8_t is_key_down); + uint8_t is_key_down, + uint16_t event_flags); /* ``` diff --git a/core/include/ldml/.gitignore b/core/include/ldml/.gitignore new file mode 100644 index 00000000000..3e39f0a4605 --- /dev/null +++ b/core/include/ldml/.gitignore @@ -0,0 +1 @@ +ldml-const-builder/ \ No newline at end of file diff --git a/core/include/ldml/keyboardprocessor_ldml.h b/core/include/ldml/keyboardprocessor_ldml.h new file mode 100644 index 00000000000..220dabb5da1 --- /dev/null +++ b/core/include/ldml/keyboardprocessor_ldml.h @@ -0,0 +1,122 @@ + +/* + Copyright: Copyright (C) 2022-2023 SIL International. + Authors: srl295 + This file provides constants for the KMX Plus (LDML support) binary format, + to be shared between TypeScript and C++ via the generator (below) +*/ + +// +// Generated File - do not edit +// +// This file is generated by core/tools/ldml-const-builder/build.sh +// based on core/include/ldml/keyboardprocessor_ldml.ts +// + +#pragma once + +#define LDML_BKSP_FLAGS_ERROR 0x1 +#define LDML_CLDR_IMPLIED_KEYS_IMPORT "techpreview/keys-Latn-implied.xml" +#define LDML_CLDR_IMPORT_BASE "cldr" +#define LDML_CLDR_VERSION_LATEST "techpreview" +#define LDML_CLDR_VERSION_TECHPREVIEW "techpreview" +#define LDML_ELEM_FLAGS_ORDER_BITSHIFT 0x10 +#define LDML_ELEM_FLAGS_ORDER_MASK 0xFF0000 +#define LDML_ELEM_FLAGS_PREBASE 0x4 +#define LDML_ELEM_FLAGS_TERTIARY_BASE 0x2 +#define LDML_ELEM_FLAGS_TERTIARY_BITSHIFT 0x18 +#define LDML_ELEM_FLAGS_TERTIARY_MASK 0xFF000000 +#define LDML_ELEM_FLAGS_UNICODE_SET 0x1 +#define LDML_FINL_FLAGS_ERROR 0x1 +#define LDML_KEYS_FLICK_FLAGS_EXTEND 0x1 +#define LDML_KEYS_KEY_FLAGS_EXTEND 0x1 +#define LDML_KEYS_KEY_FLAGS_GAP 0x2 +#define LDML_KEYS_KEY_FLAGS_NOTRANSFORM 0x4 +#define LDML_KEYS_MOD_ALL 0x11F +#define LDML_KEYS_MOD_ALT 0xC +#define LDML_KEYS_MOD_ALTL 0x4 +#define LDML_KEYS_MOD_ALTR 0x8 +#define LDML_KEYS_MOD_CAPS 0x100 +#define LDML_KEYS_MOD_CTRL 0x3 +#define LDML_KEYS_MOD_CTRLL 0x1 +#define LDML_KEYS_MOD_CTRLR 0x2 +#define LDML_KEYS_MOD_NONE 0x0 +#define LDML_KEYS_MOD_SHIFT 0x10 +#define LDML_LAYR_LIST_HARDWARE_ABNT2 0x1 +#define LDML_LAYR_LIST_HARDWARE_ISO 0x2 +#define LDML_LAYR_LIST_HARDWARE_JIS 0x3 +#define LDML_LAYR_LIST_HARDWARE_TOUCH 0x0 +#define LDML_LAYR_LIST_HARDWARE_US 0x4 +#define LDML_LENGTH_BKSP 0xC +#define LDML_LENGTH_BKSP_ITEM 0x10 +#define LDML_LENGTH_DISP 0x10 +#define LDML_LENGTH_DISP_ITEM 0x8 +#define LDML_LENGTH_ELEM 0xC +#define LDML_LENGTH_ELEM_ITEM 0x8 +#define LDML_LENGTH_ELEM_ITEM_ELEMENT 0x8 +#define LDML_LENGTH_FINL 0x8 +#define LDML_LENGTH_FINL_ITEM 0x10 +#define LDML_LENGTH_HEADER 0x8 +#define LDML_LENGTH_KEYS 0x18 +#define LDML_LENGTH_KEYS_FLICK_ELEMENT 0xC +#define LDML_LENGTH_KEYS_FLICK_LIST 0xC +#define LDML_LENGTH_KEYS_KEY 0x24 +#define LDML_LENGTH_KEYS_KMAP 0xC +#define LDML_LENGTH_LAYR 0x18 +#define LDML_LENGTH_LAYR_ENTRY 0x10 +#define LDML_LENGTH_LAYR_KEY 0x4 +#define LDML_LENGTH_LAYR_LIST 0x10 +#define LDML_LENGTH_LAYR_ROW 0x8 +#define LDML_LENGTH_LIST 0x10 +#define LDML_LENGTH_LIST_INDEX 0x4 +#define LDML_LENGTH_LIST_ITEM 0x8 +#define LDML_LENGTH_LOCA 0xC +#define LDML_LENGTH_LOCA_ITEM 0x4 +#define LDML_LENGTH_META 0x24 +#define LDML_LENGTH_NAME 0xC +#define LDML_LENGTH_NAME_ITEM 0x4 +#define LDML_LENGTH_ORDR 0xC +#define LDML_LENGTH_ORDR_ITEM 0x8 +#define LDML_LENGTH_SECT 0x10 +#define LDML_LENGTH_SECT_ITEM 0x8 +#define LDML_LENGTH_STRS 0xC +#define LDML_LENGTH_STRS_ITEM 0x8 +#define LDML_LENGTH_TRAN 0xC +#define LDML_LENGTH_TRAN_ITEM 0x10 +#define LDML_LENGTH_VKEY 0xC +#define LDML_LENGTH_VKEY_ITEM 0x8 +#define LDML_META_SETTINGS_FALLBACK_OMIT 0x1 +#define LDML_META_SETTINGS_TRANSFORMFAILURE_OMIT 0x2 +#define LDML_META_SETTINGS_TRANSFORMPARTIAL_HIDE 0x4 +#define LDML_SECTIONID_BKSP 0x70736B62 /* "bksp" */ +#define LDML_SECTIONNAME_BKSP "bksp" +#define LDML_SECTIONID_DISP 0x70736964 /* "disp" */ +#define LDML_SECTIONNAME_DISP "disp" +#define LDML_SECTIONID_ELEM 0x6D656C65 /* "elem" */ +#define LDML_SECTIONNAME_ELEM "elem" +#define LDML_SECTIONID_FINL 0x6C6E6966 /* "finl" */ +#define LDML_SECTIONNAME_FINL "finl" +#define LDML_SECTIONID_KEYS 0x7379656B /* "keys" */ +#define LDML_SECTIONNAME_KEYS "keys" +#define LDML_SECTIONID_LAYR 0x7279616C /* "layr" */ +#define LDML_SECTIONNAME_LAYR "layr" +#define LDML_SECTIONID_LIST 0x7473696C /* "list" */ +#define LDML_SECTIONNAME_LIST "list" +#define LDML_SECTIONID_LOCA 0x61636F6C /* "loca" */ +#define LDML_SECTIONNAME_LOCA "loca" +#define LDML_SECTIONID_META 0x6174656D /* "meta" */ +#define LDML_SECTIONNAME_META "meta" +#define LDML_SECTIONID_NAME 0x656D616E /* "name" */ +#define LDML_SECTIONNAME_NAME "name" +#define LDML_SECTIONID_ORDR 0x7264726F /* "ordr" */ +#define LDML_SECTIONNAME_ORDR "ordr" +#define LDML_SECTIONID_SECT 0x74636573 /* "sect" */ +#define LDML_SECTIONNAME_SECT "sect" +#define LDML_SECTIONID_STRS 0x73727473 /* "strs" */ +#define LDML_SECTIONNAME_STRS "strs" +#define LDML_SECTIONID_TRAN 0x6E617274 /* "tran" */ +#define LDML_SECTIONNAME_TRAN "tran" +#define LDML_SECTIONID_VKEY 0x79656B76 /* "vkey" */ +#define LDML_SECTIONNAME_VKEY "vkey" +#define LDML_TRAN_FLAGS_ERROR 0x1 +#define LDML_VERSION "1.0" diff --git a/core/include/ldml/keyboardprocessor_ldml.ts b/core/include/ldml/keyboardprocessor_ldml.ts new file mode 100644 index 00000000000..457d68e8561 --- /dev/null +++ b/core/include/ldml/keyboardprocessor_ldml.ts @@ -0,0 +1,566 @@ +/* + Copyright: Copyright (C) 2022 SIL International. + Authors: srl295, mcdurdin + This file provides constants for the KMX Plus (LDML support) binary format, + to be shared between TypeScript and C++ via the generator (below) +*/ + + +// NOTICE! +// +// If you update this file, you *must* be sure to re-run +// +// core/tools/ldml-const-builder/build.sh clean build run +// +// To update keyboardprocessor_ldml.h, and commit the result. +// +// It is not updated automatically. + + +/** + * Defines the section identifiers and ensures that we include each and every + * one of them in the `sections` block and gives us a type which we can iterate + * through. + */ +export type SectionIdent = +// Keep this sorted, but with `sect` as the first entry. + 'sect' | + 'bksp' | + 'disp' | + 'elem' | + 'finl' | + 'keys' | + 'layr' | + 'list' | + 'loca' | + 'meta' | + 'name' | + 'ordr' | + 'strs' | + 'tran' | + 'vkey'; + + +type SectionMap = { + [id in SectionIdent]: SectionIdent; +} + +// TODO-LDML: namespace com.keyman.core.ldml { +/** + * Constants for the KMXPlus data format + * These are shared between the data access layer and the compiler. + * Note that the section IDs (section_keys etc.) are 32 bit hex + * values that are designed to appear as text when written in little endian + * format, so 0x7379656b = 'keys' + */ +class Constants { + /** + * The version of the LDML processor + */ + readonly version = '1.0'; + /** + * The techpreview CLDR version + */ + readonly cldr_version_techpreview = 'techpreview'; + /** + * The latest CLDR version + */ + readonly cldr_version_latest = this.cldr_version_techpreview; + /** + * import base + */ + readonly cldr_import_base = 'cldr'; + /** + * implied keys file + */ + readonly cldr_implied_keys_import = `${this.cldr_version_techpreview}/keys-Latn-implied.xml`; + /** + * Length of a raw section header, in bytes + */ + readonly length_header = 8; + + /* ------------------------------------------------------------------ + * sect section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'sect' section, not including entries + */ + readonly length_sect = 16; + /** + * Length of each item in the 'sect' section variable part + */ + readonly length_sect_item = 8; + + /* ------------------------------------------------------------------ + * bksp section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'bksp' section, not including entries + */ + readonly length_bksp = 12; + /** + * Length of each item in the 'bksp' section variable part + */ + readonly length_bksp_item = 16; + /** + * bitwise or value for error="fail" in transform + */ + readonly bksp_flags_error = 0x0001; + + /* ------------------------------------------------------------------ + * disp section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'disp' section, not including entries + */ + readonly length_disp = 16; + /** + * Length of each entry in the 'disp' variable part + */ + readonly length_disp_item = 8; + + /* ------------------------------------------------------------------ + * elem section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'elem' section, not including entries + */ + readonly length_elem = 12; + /** + * Length of each elem string in the 'elem' section variable part + */ + readonly length_elem_item = 8; + /** + * Length of each element in an elem string + */ + readonly length_elem_item_element = 8; + + /** + * bitwise or value for unicode_set in elem[elemstr][element].flags. + * If bit is 1, then 'element' is a UnicodeSet string. + * If bit is 0, then 'element' is a UTF-32LE codepoint + * + * `unicode_set = flags & elem_flags_unicode_set` + */ + readonly elem_flags_unicode_set = 0x00000001; + + /** + * bitwise or value for tertiary_base in elem[elemstr][element].flags. + * If bit is 1, then tertiary_base is true. + * If bit is 0, then tertiary_base is false. + * + * Used only for `ordr`-type element strings. + * + * `tertiary_base = flags & elem_flags_tertiary_base` + */ + readonly elem_flags_tertiary_base = 0x00000002; + + /** + * bitwise or value for tertiary_base in elem[elemstr][element].flags. + * If bit is 1, then prebase is true. + * If bit is 0, then prebase is false. + * + * Used only for `ordr`-type element strings. + * + * `prebase = flags & elem_flags_prebase` + */ + readonly elem_flags_prebase = 0x00000004; + + /** + * bitwise mask for order in elem[elemstr][element].flags. + * + * Used only for `ordr`-type element strings. 1 byte signed integer. + * + * `order = (flags & elem_flags_order_mask) >> elem_flags_order_bitshift` + */ + readonly elem_flags_order_mask = 0x00FF0000; + + /** + * bit shift for order in elem[elemstr][element].flags. + * + * Used only for `ordr`-type element strings. + * + * `order = (flags & elem_flags_order_mask) >> elem_flags_order_bitshift` + */ + readonly elem_flags_order_bitshift = 16; + + /** + * bitwise mask for tertiary sort in elem[elemstr][element].flags. + * + * Used only for `ordr`-type element strings. 1 byte signed integer. + * + * `tertiary = (flags & elem_flags_tertiary_mask) >> elem_flags_tertiary_bitshift` + */ + readonly elem_flags_tertiary_mask = 0xFF000000; + + /** + * bit shift for tertiary sort in elem[elemstr][element].flags. + * + * Used only for `ordr`-type element strings. 1 byte signed integer. + * + * `order = (flags & elem_flags_tertiary_mask) >> elem_flags_tertiary_bitshift` + */ + readonly elem_flags_tertiary_bitshift = 24; + + /* ------------------------------------------------------------------ + * finl section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'finl' section, not including entries + */ + readonly length_finl = 8; + /** + * Length of each item in the 'finl' section variable part + */ + readonly length_finl_item = 16; + /** + * bitwise or value for error="fail" in transform + */ + readonly finl_flags_error = 0x0001; + + /* ------------------------------------------------------------------ + * keys section is now keys.kmap + ------------------------------------------------------------------ */ + + /** + * Constant for no modifiers + */ + readonly keys_mod_none = 0; + /** + * bitmask for Left Alt modifier key + */ + readonly keys_mod_altL = 1 << 2; + /** + * bitmask for Right Alt (AltGr) modifier key + */ + readonly keys_mod_altR = 1 << 3; + /** + * bitmask for either Alt (Windows) or Option (Apple) modifier keys + */ + readonly keys_mod_alt = this.keys_mod_altL | this.keys_mod_altR; + /** + * bitmask for Caps modifier key + */ + readonly keys_mod_caps = 1 << 8; + /** + * bitmask for Left control modifier key + */ + readonly keys_mod_ctrlL = 1 << 0; + /** + * bitmask for Right control modifier key + */ + readonly keys_mod_ctrlR = 1 << 1; + /** + * bitmask for either Control modifier key + */ + readonly keys_mod_ctrl = this.keys_mod_ctrlL | this.keys_mod_ctrlR; + /** + * bitmask for either shift. + */ + readonly keys_mod_shift = 1 << 4; + + /** + * Convenience map for modifiers + */ + readonly keys_mod_map: Map = new Map( + [ + ["none", this.keys_mod_none], + ["alt", this.keys_mod_alt], + ["altL", this.keys_mod_altL], + ["altR", this.keys_mod_altR], + ["caps", this.keys_mod_caps], + ["ctrl", this.keys_mod_ctrl], + ["ctrlL", this.keys_mod_ctrlL], + ["ctrlR", this.keys_mod_ctrlR], + ["shift", this.keys_mod_shift], + ] + ); + + /** + * a mask combining all valid modifier bits + */ + readonly keys_mod_all: number = Array.from(this.keys_mod_map.values()).reduce((p, v) => (p | v), this.keys_mod_none); + + /* ------------------------------------------------------------------ + * keys section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'keys' section not including variable parts + */ + readonly length_keys = 24; + /** + * Length of each item in the 'keys' keys sub-table + */ + readonly length_keys_key = 36; + /** + * Length of each item in the 'keys' flick lists sub-table + */ + readonly length_keys_flick_list = 12; + /** + * Length of each item in the 'keys' flick elements sub-table + */ + readonly length_keys_flick_element = 12; + /** + * Length of each item in the 'keys.kmap' key map subtable + */ + readonly length_keys_kmap = 12; + + /** + * 0 if to is a char, 1 if it is a string + */ + readonly keys_key_flags_extend = 0x00000001; + + /** + * 1 if the key is a gap + */ + readonly keys_key_flags_gap = 0x00000002; + + /** + * 1 if the key is transform=no + */ + readonly keys_key_flags_notransform = 0x00000004; + + /** + * 0 if to is a char, 1 if it is a string + */ + readonly keys_flick_flags_extend = 0x00000001; + + /* ------------------------------------------------------------------ + * layr section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'layr' section not including variable parts + */ + readonly length_layr = 24; + /** + * Length of each layer list in the 'layr' section variable part + */ + readonly length_layr_list = 16; + /** + * for the 'hardware' field indicating a touch keyboard, non-hardware + */ + readonly layr_list_hardware_touch = 0; + /** + * for the 'hardware' field indicating an abnt2 layout + */ + readonly layr_list_hardware_abnt2 = 1; + /** + * for the 'hardware' field indicating an iso layout + */ + readonly layr_list_hardware_iso = 2; + /** + * for the 'hardware' field indicating a jis layout + */ + readonly layr_list_hardware_jis = 3; + /** + * for the 'hardware' field indicating a us layout + */ + readonly layr_list_hardware_us = 4; + /** + * Convenience map of layr_list_hardware field values + */ + readonly layr_list_hardware_map: Map = new Map( + [ + ["touch", this.layr_list_hardware_touch], + ["abnt2", this.layr_list_hardware_abnt2], + ["iso", this.layr_list_hardware_iso], + ["jis", this.layr_list_hardware_jis], + ["us", this.layr_list_hardware_us], + ] + ); + /** + * Length of each layer entry in the 'layr' section variable part + */ + readonly length_layr_entry = 16; + /** + * Length of each row entry in the 'layr' section variable part + */ + readonly length_layr_row = 8; + /** + * Length of each key entry in the 'layr' section variable part + */ + readonly length_layr_key = 4; + + /* ------------------------------------------------------------------ + * list section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'list' section not including variable parts + */ + readonly length_list = 16; + /** + * Length of each list item in the 'list' list section variable part + */ + readonly length_list_item = 8; + /** + * Length of each list item in the 'list' indices section variable part + */ + readonly length_list_index = 4; + + /* ------------------------------------------------------------------ + * loca section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'loca' section not including variable parts + */ + readonly length_loca = 12; + /** + * Length of each item in the 'loca' section variable part + */ + readonly length_loca_item = 4; + + /* ------------------------------------------------------------------ + * meta section + ------------------------------------------------------------------ */ + + /** + * length of the 'meta' section + */ + readonly length_meta = 36; + /** + * bitwise or value for fallback=omit in meta.settings + */ + readonly meta_settings_fallback_omit = 1; + /** + * bitwise or value for transformFailure=omit in meta.settings + */ + readonly meta_settings_transformFailure_omit = 2; + /** + * bitwise or value for transformPartial=hide in meta.settings + */ + readonly meta_settings_transformPartial_hide = 4; + + /* ------------------------------------------------------------------ + * name section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'name' section not including variable parts + */ + readonly length_name = 12; + /** + * Length of each item in the 'name' section variable part + */ + readonly length_name_item = 4; + + /* ------------------------------------------------------------------ + * ordr section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'ordr' section, not including entries + */ + readonly length_ordr = 12; + /** + * Length of each item in the 'ordr' section variable part + */ + readonly length_ordr_item = 8; + + /* ------------------------------------------------------------------ + * strs section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'strs' section not including variable parts + */ + readonly length_strs = 12; + /** + * Length of each item in the 'strs' section variable part + */ + readonly length_strs_item = 8; + + /* ------------------------------------------------------------------ + * tran section + ------------------------------------------------------------------ */ + + /** + * Minimum length of the 'tran' section, not including entries + */ + readonly length_tran = 12; + /** + * Length of each item in the 'tran' section variable part + */ + readonly length_tran_item = 16; + /** + * bitwise or value for error="fail" in transform + */ + readonly tran_flags_error = 0x0001; + + /* ------------------------------------------------------------------ + * 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; + + /** + * All section IDs. + */ + readonly section: SectionMap = { + // keep this sorted + bksp: 'bksp', + disp: 'disp', + elem: 'elem', + finl: 'finl', + keys: 'keys', + layr: 'layr', + list: 'list', + loca: 'loca', + meta: 'meta', + name: 'name', + ordr: 'ordr', + sect: 'sect', + strs: 'strs', + tran: 'tran', + vkey: 'vkey', + }; + + /** + * Use to convert 4-char string into hex + * @param id section id such as 'sect' + * @returns hex ID such as 0x74636573 + */ + hex_section_id(id:string) { + if(!id || typeof id !== 'string' || !id.match(/^[a-z0-9]{4}$/)) { + throw Error(`hex_section_id(${id}) - need a 4-character alphanumeric lower-case string`); + } + let r = 0; + for (let i = 3; i>=0; i--) { + r = (r << 8 | id.charCodeAt(i)); + } + return r; + }; + + /** + * Use to convert hex into 4-char string + * @param hex section ID such as 0x74636573 + * @returns string such as 'sect' + */ + str_section_id(hex:number) : string { + let chars : string[] = []; + for (let i = 3; i>=0; i--) { + chars.push(String.fromCharCode(hex & 0xFF)); + hex >>= 8; + } + return chars.join(''); + } +}; + +export const constants = new Constants(); + +// } diff --git a/core/include/ldml/ldml-const-builder.ts b/core/include/ldml/ldml-const-builder.ts new file mode 100644 index 00000000000..c08e74dddc6 --- /dev/null +++ b/core/include/ldml/ldml-const-builder.ts @@ -0,0 +1,65 @@ +/* + Copyright: Copyright (C) 2022 SIL International. + Authors: srl295 + This tool generates a .h version of the keyboardprocessor_ldml.ts file +*/ + +import { constants } from './keyboardprocessor_ldml.js'; + +const keys = Object.keys(constants); +keys.sort(); +console.log(` +/* + Copyright: Copyright (C) 2022-2023 SIL International. + Authors: srl295 + This file provides constants for the KMX Plus (LDML support) binary format, + to be shared between TypeScript and C++ via the generator (below) +*/ + +// +// Generated File - do not edit +// +// This file is generated by core/tools/ldml-const-builder/build.sh +// based on core/include/ldml/keyboardprocessor_ldml.ts +// + +#pragma once +`); + +let errs = 0; + +for (const key of keys) { + const value: any = constants[key as keyof typeof constants]; + const upkey = key.toUpperCase(); + const type = typeof value; + if (type === 'number') { + console.log(`#define LDML_${upkey} 0x${value.toString(16).toUpperCase()}`); + } else if (type === 'string') { + console.log(`#define LDML_${upkey} "${value}"`); + } else if (key === 'section') { + // handle section table + const subkeys = Object.keys(value); + subkeys.sort(); + for (const subkey of subkeys) { + const upsubkey = subkey.toUpperCase(); + const subvalue = value[subkey]; + if (subvalue !== subkey) { + // "can't happen" because tsc would complain + console.error(`In the SectionMap: ${subkey}: '${subvalue}' - expected key and value to match.`); + errs++; + } + const asnum = constants.hex_section_id(subkey); + console.log(`#define LDML_${upkey}ID_${upsubkey} 0x${asnum.toString(16).toUpperCase()} /* "${subkey}" */`); + console.log(`#define LDML_${upkey}NAME_${upsubkey} "${subkey}"`); + } + } else if (key.endsWith('_map') || type === 'function') { + // ignored + } else { + console.error(`Don’t know what to do with constants[${key}] of type ${type}`); + errs++; + } +} + +if (errs != 0) { + throw Error(`Fail: ${errs} error(s), see above.`); +} diff --git a/core/include/ldml/package.json b/core/include/ldml/package.json new file mode 100644 index 00000000000..d74a720d1ea --- /dev/null +++ b/core/include/ldml/package.json @@ -0,0 +1,17 @@ +{ + "name": "@keymanapp/ldml-keyboard-constants", + "description": "Keyman LDML keyboard constants", + "keywords": [ + "keyboard", + "keyman", + "ldml", + "unicode" + ], + "license": "MIT", + "type": "module", + "main": "build/keyboardprocessor_ldml.js", + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" + } +} diff --git a/core/include/ldml/tsconfig.build.json b/core/include/ldml/tsconfig.build.json new file mode 100644 index 00000000000..17c809a857a --- /dev/null +++ b/core/include/ldml/tsconfig.build.json @@ -0,0 +1,16 @@ +{ + "extends": "../../../tsconfig.esm-base.json", + "compilerOptions": { + "composite": true, + "declaration": true, + "rootDir": ".", + "outDir": "ldml-const-builder/", + }, + "exclude": [ + "node_modules" + ], + "files": [ + "keyboardprocessor_ldml.ts", + "ldml-const-builder.ts" + ] +} diff --git a/core/include/ldml/tsconfig.json b/core/include/ldml/tsconfig.json new file mode 100644 index 00000000000..b89c96a0181 --- /dev/null +++ b/core/include/ldml/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../../../tsconfig-base.json", + "compilerOptions": { + "composite": true, + "declaration": true, + "module": "es2020", + "moduleResolution": "node", + "rootDir": ".", + "outDir": "build/", + }, + "exclude": [ + "node_modules" + ], + "files": [ + "keyboardprocessor_ldml.ts" + ] +} diff --git a/core/meson.build b/core/meson.build index 5f530aae3dd..47b8860389e 100644 --- a/core/meson.build +++ b/core/meson.build @@ -6,7 +6,7 @@ # project('keyboardprocessor', 'cpp', 'c', - version: run_command(find_program('getversion.bat', 'getversion.sh')).stdout().strip(), + version: run_command(find_program('getversion.bat', 'getversion.sh'), check:true).stdout().strip(), license: 'MIT', default_options : ['buildtype=release', 'cpp_std=c++14', @@ -36,7 +36,12 @@ cc = meson.get_compiler('c') # For now, we use KMN_KBP to inject the km::kbp::kmx namespace defns = ['-DKMN_KBP'] +# #define DEBUG when we are on a debug build +if get_option('buildtype') == 'debug' + add_global_arguments('-DDEBUG', language : 'cpp') +endif + subdir('doc') subdir('include') subdir('src') -subdir('tests') +subdir('tests') \ No newline at end of file diff --git a/core/meson_options.txt b/core/meson_options.txt new file mode 100644 index 00000000000..a4ef5ef3e54 --- /dev/null +++ b/core/meson_options.txt @@ -0,0 +1 @@ +option('keyman_core_tests', type: 'boolean', value: true) \ No newline at end of file diff --git a/core/src/debuglog.cpp b/core/src/debuglog.cpp new file mode 100644 index 00000000000..dc3dd95cd59 --- /dev/null +++ b/core/src/debuglog.cpp @@ -0,0 +1,465 @@ +/* + Copyright: Copyright (C) 2003-2018 SIL International. + Authors: mcdurdin +*/ +#include +#include +#include +#include +#include "debuglog.h" + +namespace km { +namespace kbp { +namespace kmx { + +#define TAB "\t" +#define NL "\n" + +/** + * \def MEDIUM_BUF_SIZ not too big, not too small +*/ +#define MEDIUM_BUF_SIZ (128 * 7) + + +#ifdef _MSC_VER +#define _USE_WINDOWS +#endif + +#ifdef _USE_WINDOWS +#define DECLSPEC_IMPORT __declspec(dllimport) +#define WINBASEAPI DECLSPEC_IMPORT +#define VOID void +#define WINAPI __stdcall + +extern "C" +__declspec(dllimport) +void +__stdcall +OutputDebugStringA( + char *lpOutputString +); + +#else + #include +#endif + +const struct modifier_names s_modifier_names[] = { + {"LCTRL", 0x0001}, // Left Control flag + {"RCTRL", 0x0002}, // Right Control flag + {"LALT", 0x0004}, // Left Alt flag + {"RALT", 0x0008}, // Right Alt flag + {"SHIFT", 0x0010}, // Either shift flag + {"CTRL-do-not-use", 0x0020}, // Either ctrl flag -- don't use this for inputs + {"ALT-do-not-use", 0x0040}, // Either alt flag -- don't use this for inputs + {"CAPS", 0x0100}, // Caps lock on + {"NCAPS", 0x0200}, // Caps lock NOT on + {"NUMLOCK", 0x0400}, // Num lock on + {"NNUMLOCK", 0x0800}, // Num lock NOT on + {"SCROLL", 0x1000}, // Scroll lock on + {"NSCROLL", 0x2000}, // Scroll lock NOT on + {NULL, 0} +}; + +const char *s_key_names[] = { + // Key Codes + "K_?00", // &H0 + "K_LBUTTON", // &H1 + "K_RBUTTON", // &H2 + "K_CANCEL", // &H3 + "K_MBUTTON", // &H4 + "K_?05", // &H5 + "K_?06", // &H6 + "K_?07", // &H7 + "K_BKSP", // &H8 + "K_TAB", // &H9 + "K_?0A", // &HA + "K_?0B", // &HB + "K_KP5", // &HC + "K_ENTER", // &HD + "K_?0E", // &HE + "K_?0F", // &HF + "K_SHIFT", // &H10 + "K_CONTROL", // &H11 + "K_ALT", // &H12 + "K_PAUSE", // &H13 + "K_CAPS", // &H14 + "K_KANJI?15", // &H15 + "K_KANJI?16", // &H16 + "K_KANJI?17", // &H17 + "K_KANJI?18", // &H18 + "K_KANJI?19", // &H19 + "K_?1A", // &H1A + "K_ESC", // &H1B + "K_KANJI?1C", // &H1C + "K_KANJI?1D", // &H1D + "K_KANJI?1E", // &H1E + "K_KANJI?1F", // &H1F + "K_SPACE", // &H20 + "K_PGUP", // &H21 + "K_PGDN", // &H22 + "K_END", // &H23 + "K_HOME", // &H24 + "K_LEFT", // &H25 + "K_UP", // &H26 + "K_RIGHT", // &H27 + "K_DOWN", // &H28 + "K_SEL", // &H29 + "K_PRINT", // &H2A + "K_EXEC", // &H2B + "K_PRTSCN", // &H2C + "K_INS", // &H2D + "K_DEL", // &H2E + "K_HELP", // &H2F + "K_0", // &H30 + "K_1", // &H31 + "K_2", // &H32 + "K_3", // &H33 + "K_4", // &H34 + "K_5", // &H35 + "K_6", // &H36 + "K_7", // &H37 + "K_8", // &H38 + "K_9", // &H39 + "K_?3A", // &H3A + "K_?3B", // &H3B + "K_?3C", // &H3C + "K_?3D", // &H3D + "K_?3E", // &H3E + "K_?3F", // &H3F + "K_?40", // &H40 + + "K_A", // &H41 + "K_B", // &H42 + "K_C", // &H43 + "K_D", // &H44 + "K_E", // &H45 + "K_F", // &H46 + "K_G", // &H47 + "K_H", // &H48 + "K_I", // &H49 + "K_J", // &H4A + "K_K", // &H4B + "K_L", // &H4C + "K_M", // &H4D + "K_N", // &H4E + "K_O", // &H4F + "K_P", // &H50 + "K_Q", // &H51 + "K_R", // &H52 + "K_S", // &H53 + "K_T", // &H54 + "K_U", // &H55 + "K_V", // &H56 + "K_W", // &H57 + "K_X", // &H58 + "K_Y", // &H59 + "K_Z", // &H5A + "K_?5B", // &H5B + "K_?5C", // &H5C + "K_?5D", // &H5D + "K_?5E", // &H5E + "K_?5F", // &H5F + "K_NP0", // &H60 + "K_NP1", // &H61 + "K_NP2", // &H62 + "K_NP3", // &H63 + "K_NP4", // &H64 + "K_NP5", // &H65 + "K_NP6", // &H66 + "K_NP7", // &H67 + "K_NP8", // &H68 + "K_NP9", // &H69 + "K_NPSTAR", // &H6A + "K_NPPLUS", // &H6B + "K_SEPARATOR", // &H6C + "K_NPMINUS", // &H6D + "K_NPDOT", // &H6E + "K_NPSLASH", // &H6F + "K_F1", // &H70 + "K_F2", // &H71 + "K_F3", // &H72 + "K_F4", // &H73 + "K_F5", // &H74 + "K_F6", // &H75 + "K_F7", // &H76 + "K_F8", // &H77 + "K_F9", // &H78 + "K_F10", // &H79 + "K_F11", // &H7A + "K_F12", // &H7B + "K_F13", // &H7C + "K_F14", // &H7D + "K_F15", // &H7E + "K_F16", // &H7F + "K_F17", // &H80 + "K_F18", // &H81 + "K_F19", // &H82 + "K_F20", // &H83 + "K_F21", // &H84 + "K_F22", // &H85 + "K_F23", // &H86 + "K_F24", // &H87 + + "K_?88", // &H88 + "K_?89", // &H89 + "K_?8A", // &H8A + "K_?8B", // &H8B + "K_?8C", // &H8C + "K_?8D", // &H8D + "K_?8E", // &H8E + "K_?8F", // &H8F + + "K_NUMLOCK", // &H90 + "K_SCROLL", // &H91 + + "K_?92", // &H92 + "K_?93", // &H93 + "K_?94", // &H94 + "K_?95", // &H95 + "K_?96", // &H96 + "K_?97", // &H97 + "K_?98", // &H98 + "K_?99", // &H99 + "K_?9A", // &H9A + "K_?9B", // &H9B + "K_?9C", // &H9C + "K_?9D", // &H9D + "K_?9E", // &H9E + "K_?9F", // &H9F + "K_?A0", // &HA0 + "K_?A1", // &HA1 + "K_?A2", // &HA2 + "K_?A3", // &HA3 + "K_?A4", // &HA4 + "K_?A5", // &HA5 + "K_?A6", // &HA6 + "K_?A7", // &HA7 + "K_?A8", // &HA8 + "K_?A9", // &HA9 + "K_?AA", // &HAA + "K_?AB", // &HAB + "K_?AC", // &HAC + "K_?AD", // &HAD + "K_?AE", // &HAE + "K_?AF", // &HAF + "K_?B0", // &HB0 + "K_?B1", // &HB1 + "K_?B2", // &HB2 + "K_?B3", // &HB3 + "K_?B4", // &HB4 + "K_?B5", // &HB5 + "K_?B6", // &HB6 + "K_?B7", // &HB7 + "K_?B8", // &HB8 + "K_?B9", // &HB9 + + "K_COLON", // &HBA + "K_EQUAL", // &HBB + "K_COMMA", // &HBC + "K_HYPHEN", // &HBD + "K_PERIOD", // &HBE + "K_SLASH", // &HBF + "K_BKQUOTE", // &HC0 + + "K_?C1", // &HC1 + "K_?C2", // &HC2 + "K_?C3", // &HC3 + "K_?C4", // &HC4 + "K_?C5", // &HC5 + "K_?C6", // &HC6 + "K_?C7", // &HC7 + "K_?C8", // &HC8 + "K_?C9", // &HC9 + "K_?CA", // &HCA + "K_?CB", // &HCB + "K_?CC", // &HCC + "K_?CD", // &HCD + "K_?CE", // &HCE + "K_?CF", // &HCF + "K_?D0", // &HD0 + "K_?D1", // &HD1 + "K_?D2", // &HD2 + "K_?D3", // &HD3 + "K_?D4", // &HD4 + "K_?D5", // &HD5 + "K_?D6", // &HD6 + "K_?D7", // &HD7 + "K_?D8", // &HD8 + "K_?D9", // &HD9 + "K_?DA", // &HDA + + "K_LBRKT", // &HDB + "K_BKSLASH", // &HDC + "K_RBRKT", // &HDD + "K_QUOTE", // &HDE + "K_oDF", // &HDF + "K_oE0", // &HE0 + "K_oE1", // &HE1 + "K_oE2", // &HE2 + "K_oE3", // &HE3 + "K_oE4", // &HE4 + + "K_?E5", // &HE5 + + "K_oE6", // &HE6 + + "K_?E7", // &HE7 + "K_?E8", // &HE8 + + "K_oE9", // &HE9 + "K_oEA", // &HEA + "K_oEB", // &HEB + "K_oEC", // &HEC + "K_oED", // &HED + "K_oEE", // &HEE + "K_oEF", // &HEF + "K_oF0", // &HF0 + "K_oF1", // &HF1 + "K_oF2", // &HF2 + "K_oF3", // &HF3 + "K_oF4", // &HF4 + "K_oF5", // &HF5 + + "K_?F6", // &HF6 + "K_?F7", // &HF7 + "K_?F8", // &HF8 + "K_?F9", // &HF9 + "K_?FA", // &HFA + "K_?FB", // &HFB + "K_?FC", // &HFC + "K_?FD", // &HFD + "K_?FE", // &HFE + "K_?FF" // &HFF +}; + +// simulation of Windows GetTickCount() +unsigned long +GetTickCount() +{ + using namespace std::chrono; + return (unsigned long) duration_cast(steady_clock::now().time_since_epoch()).count(); +} + +int DebugLog_1(const char *file, int line, const char *function, const char *fmt, ...) +{ + char fmtbuf[256]; + + va_list vars; + va_start(vars, fmt); + vsnprintf(fmtbuf, sizeof(fmtbuf) / sizeof(fmtbuf[0]), fmt, vars); // I2248 // I3547 + fmtbuf[255] = 0; + va_end(vars); + + if (!g_debug_KeymanLog) + return 0; + + char windowinfo[1024]; + snprintf(windowinfo, 1024, + "%ld" TAB //"TickCount" TAB + "%s:%d" TAB //"SourceFile" TAB + "%s" TAB //"Function" + "%s" NL, //"Message" + + GetTickCount(), //"TickCount" TAB + file, line, //"SourceFile" TAB + function, //"Function" TAB + fmtbuf); //"Message" + + if (g_debug_ToConsole) { // I3951 + ::std::cout << windowinfo << ::std::endl; // OutputDebugStringA(windowinfo); + } else { +#ifdef _USE_WINDOWS + ::std::cout << windowinfo << ::std::endl; // OutputDebugStringA(windowinfo); +#else + syslog(LOG_DEBUG, "%s", windowinfo); +#endif + } + + return 0; +} + +const char *Debug_ModifierName(KMX_UINT modifiers) { +#ifdef _MSC_VER + __declspec(thread) +#endif + static char buf[256]; + buf[0] = 0; + for(int i = 0; s_modifier_names[i].name; i++) + if (modifiers & s_modifier_names[i].modifier) { + strcat(buf, " "); + strcat(buf, s_modifier_names[i].name); + } + + if (*buf) return buf + 1; + return "Unmodified"; +} + +const char *Debug_VirtualKey(KMX_WORD vk) { +#ifdef _MSC_VER + __declspec(thread) +#endif + static char buf[256]; + if (!ShouldDebug()) { + return ""; + } + + if (vk < 256) { + snprintf(buf, 256, "['%s' 0x%x]", s_key_names[vk], vk); + } + else { + snprintf(buf, 256, "[0x%x]", vk); + } + return buf; +} + +const char *Debug_UnicodeString(PKMX_WCHAR s, int x) { + if (!ShouldDebug()) { + return ""; + } +#ifdef _MSC_VER + __declspec(thread) +#endif + static char bufout[2][MEDIUM_BUF_SIZ]; + KMX_WCHAR *p; + char *q; + bufout[x][0] = 0; + for (p = s, q = bufout[x]; *p && (p - s < 128); p++) + { + snprintf(q, MEDIUM_BUF_SIZ - (q - bufout[x]), "U+%4.4X ", *p); + q = strchr(q, 0); + } + //WideCharToMultiByte(CP_ACP, 0, buf, -1, bufout, 128, NULL, NULL); + return bufout[x]; +} + +const char *Debug_UnicodeString(::std::u16string s, int x) { + if (!ShouldDebug()) { + return ""; + } +#ifdef _MSC_VER + __declspec(thread) +#endif + static char bufout[2][MEDIUM_BUF_SIZ]; + auto p = s.begin(); + char *q; + bufout[x][0] = 0; + for (q = bufout[x]; (intptr_t)(q-bufout[x]) < (128*7) && p != s.end(); p++) + { + snprintf(q, MEDIUM_BUF_SIZ - (q - bufout[x]), "U+%4.4X ", *p); + q = strchr(q, 0); + } + return bufout[x]; +} + +void write_console(KMX_BOOL error, const wchar_t *fmt, ...) { + if (!g_silent || error) { + va_list vars; + va_start(vars, fmt); + vwprintf(fmt, vars); + va_end(vars); + } +} + +} +} +} diff --git a/core/src/debuglog.h b/core/src/debuglog.h new file mode 100644 index 00000000000..c68b88d1be1 --- /dev/null +++ b/core/src/debuglog.h @@ -0,0 +1,53 @@ +/* Debugging */ + +#include + +namespace km { +namespace kbp { +namespace kmx { + +extern KMX_BOOL g_debug_ToConsole, g_debug_KeymanLog, g_silent; + +struct modifier_names { + const char *name; + uint16_t modifier; +}; + +extern const struct modifier_names s_modifier_names[]; +extern const char *s_key_names[]; + +#ifdef _MSC_VER +#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0) +#define console_error(msg,...) write_console(TRUE, (msg), __VA_ARGS__) +#define console_log(msg,...) write_console(FALSE, (msg), __VA_ARGS__) +#else +#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0) +#define console_error(msg,...) write_console(TRUE, (msg), ##__VA_ARGS__) +#define console_log(msg,...) write_console(FALSE, (msg), ##__VA_ARGS__) +#endif + +int DebugLog_1(const char *file, int line, const char *function, const char *fmt, ...); +const char *Debug_VirtualKey(KMX_WORD vk); +/** + * @param s PKMX_WCHAR to output + * @param x temporary buffer (0 or 1) to write to + * @return pointer to temporary buffer + */ +const char *Debug_UnicodeString(PKMX_WCHAR s, int x = 0); +/** + * @param s std::u16string to output + * @param x temporary buffer (0 or 1) to write to + * @return pointer to temporary buffer + */ +const char *Debug_UnicodeString(std::u16string s, int x = 0); +const char *Debug_ModifierName(KMX_UINT modifiers); + +inline KMX_BOOL ShouldDebug() { + return g_debug_KeymanLog; +} + +void write_console(KMX_BOOL error, const wchar_t *fmt, ...); + +} +} +} \ No newline at end of file diff --git a/core/src/km_kbp_keyboard_api.cpp b/core/src/km_kbp_keyboard_api.cpp index 759385dcf26..13f3cafc45c 100644 --- a/core/src/km_kbp_keyboard_api.cpp +++ b/core/src/km_kbp_keyboard_api.cpp @@ -9,21 +9,28 @@ into keyboard.hpp */ #include +#include #include #include "keyboard.hpp" #include "processor.hpp" #include "kmx/kmx_processor.hpp" +#include "ldml/ldml_processor.hpp" #include "mock/mock_processor.hpp" using namespace km::kbp; namespace { - abstract_processor * processor_factory(path const & kb_path) - { + abstract_processor * processor_factory(path const & kb_path) { // Some legacy packages may include upper-case file extensions + // TODO-LDML: move file io out of core and into engine if (kb_path.suffix() == ".kmx" || kb_path.suffix() == ".KMX") { + std::vector buf; + if(ldml_processor::is_kmxplus_file(kb_path, buf)) { + abstract_processor * result = new ldml_processor(kb_path, buf); + return result; + } return new kmx_processor(kb_path); } else if (kb_path.suffix() == ".mock") { diff --git a/core/src/km_kbp_processevent_api.cpp b/core/src/km_kbp_processevent_api.cpp index 57b9310754e..63c1833e877 100644 --- a/core/src/km_kbp_processevent_api.cpp +++ b/core/src/km_kbp_processevent_api.cpp @@ -43,12 +43,13 @@ km_kbp_status km_kbp_process_event(km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state, - uint8_t is_key_down) { + uint8_t is_key_down, + uint16_t event_flags) { assert(state != nullptr); if(state == nullptr) { return KM_KBP_STATUS_INVALID_ARGUMENT; } - return state->processor().process_event(state, vk, modifier_state, is_key_down); + return state->processor().process_event(state, vk, modifier_state, is_key_down, event_flags); } km_kbp_status diff --git a/core/src/kmx/kmx_actions.cpp b/core/src/kmx/kmx_actions.cpp index ef944a6cdb6..dabf8f429a1 100644 --- a/core/src/kmx/kmx_actions.cpp +++ b/core/src/kmx/kmx_actions.cpp @@ -45,11 +45,12 @@ KMX_BOOL KMX_Actions::QueueAction(int ItemType, KMX_DWORD dwData) break; case QIT_CHAR: - if(Uni_IsSMP(dwData)) { - m_context->Add(Uni_UTF32ToSurrogate1(dwData)); - m_context->Add(Uni_UTF32ToSurrogate2(dwData)); - } else { - m_context->Add((KMX_WORD) dwData); + { + char16_single buf; + int len = Utf32CharToUtf16(dwData, buf); + for(int i=0; iAdd(buf.ch[i]); + } } break; diff --git a/core/src/kmx/kmx_base.h b/core/src/kmx/kmx_base.h index 562998472ca..7fc335cfc46 100644 --- a/core/src/kmx/kmx_base.h +++ b/core/src/kmx/kmx_base.h @@ -49,7 +49,7 @@ typedef struct tagKEYBOARD KMX_DWORD dwFileVersion; // Version of the file - Keyman 4.0 is 0x0400 - KMX_DWORD dwCheckSum; // As stored in keyboard + KMX_DWORD dwCheckSum; // As stored in keyboard. DEPRECATED as of 16.0 KMX_DWORD xxkbdlayout; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts KMX_DWORD IsRegistered; // layout id, from same key KMX_DWORD version; // keyboard version diff --git a/core/src/kmx/kmx_consts.cpp b/core/src/kmx/kmx_consts.cpp index 199e3ff45ad..26457469498 100644 --- a/core/src/kmx/kmx_consts.cpp +++ b/core/src/kmx/kmx_consts.cpp @@ -6,7 +6,7 @@ namespace kbp { namespace kmx { const struct char_to_vkey s_char_to_vkey[] = { - {KM_KBP_VKEY_SPACE, 0, 0}, // + {KM_KBP_VKEY_SPACE, 0, 0}, // {'1', 1, 0}, // ! {KM_KBP_VKEY_QUOTE, 1, 0}, // " {'3', 1, 0}, // # @@ -104,295 +104,6 @@ const struct char_to_vkey s_char_to_vkey[] = { {0, 0, 0} }; -const char *s_key_names[] = { - // Key Codes - "K_?00", // &H0 - "K_LBUTTON", // &H1 - "K_RBUTTON", // &H2 - "K_CANCEL", // &H3 - "K_MBUTTON", // &H4 - "K_?05", // &H5 - "K_?06", // &H6 - "K_?07", // &H7 - "K_BKSP", // &H8 - "K_TAB", // &H9 - "K_?0A", // &HA - "K_?0B", // &HB - "K_KP5", // &HC - "K_ENTER", // &HD - "K_?0E", // &HE - "K_?0F", // &HF - "K_SHIFT", // &H10 - "K_CONTROL", // &H11 - "K_ALT", // &H12 - "K_PAUSE", // &H13 - "K_CAPS", // &H14 - "K_KANJI?15", // &H15 - "K_KANJI?16", // &H16 - "K_KANJI?17", // &H17 - "K_KANJI?18", // &H18 - "K_KANJI?19", // &H19 - "K_?1A", // &H1A - "K_ESC", // &H1B - "K_KANJI?1C", // &H1C - "K_KANJI?1D", // &H1D - "K_KANJI?1E", // &H1E - "K_KANJI?1F", // &H1F - "K_SPACE", // &H20 - "K_PGUP", // &H21 - "K_PGDN", // &H22 - "K_END", // &H23 - "K_HOME", // &H24 - "K_LEFT", // &H25 - "K_UP", // &H26 - "K_RIGHT", // &H27 - "K_DOWN", // &H28 - "K_SEL", // &H29 - "K_PRINT", // &H2A - "K_EXEC", // &H2B - "K_PRTSCN", // &H2C - "K_INS", // &H2D - "K_DEL", // &H2E - "K_HELP", // &H2F - "K_0", // &H30 - "K_1", // &H31 - "K_2", // &H32 - "K_3", // &H33 - "K_4", // &H34 - "K_5", // &H35 - "K_6", // &H36 - "K_7", // &H37 - "K_8", // &H38 - "K_9", // &H39 - "K_?3A", // &H3A - "K_?3B", // &H3B - "K_?3C", // &H3C - "K_?3D", // &H3D - "K_?3E", // &H3E - "K_?3F", // &H3F - "K_?40", // &H40 - - "K_A", // &H41 - "K_B", // &H42 - "K_C", // &H43 - "K_D", // &H44 - "K_E", // &H45 - "K_F", // &H46 - "K_G", // &H47 - "K_H", // &H48 - "K_I", // &H49 - "K_J", // &H4A - "K_K", // &H4B - "K_L", // &H4C - "K_M", // &H4D - "K_N", // &H4E - "K_O", // &H4F - "K_P", // &H50 - "K_Q", // &H51 - "K_R", // &H52 - "K_S", // &H53 - "K_T", // &H54 - "K_U", // &H55 - "K_V", // &H56 - "K_W", // &H57 - "K_X", // &H58 - "K_Y", // &H59 - "K_Z", // &H5A - "K_?5B", // &H5B - "K_?5C", // &H5C - "K_?5D", // &H5D - "K_?5E", // &H5E - "K_?5F", // &H5F - "K_NP0", // &H60 - "K_NP1", // &H61 - "K_NP2", // &H62 - "K_NP3", // &H63 - "K_NP4", // &H64 - "K_NP5", // &H65 - "K_NP6", // &H66 - "K_NP7", // &H67 - "K_NP8", // &H68 - "K_NP9", // &H69 - "K_NPSTAR", // &H6A - "K_NPPLUS", // &H6B - "K_SEPARATOR", // &H6C - "K_NPMINUS", // &H6D - "K_NPDOT", // &H6E - "K_NPSLASH", // &H6F - "K_F1", // &H70 - "K_F2", // &H71 - "K_F3", // &H72 - "K_F4", // &H73 - "K_F5", // &H74 - "K_F6", // &H75 - "K_F7", // &H76 - "K_F8", // &H77 - "K_F9", // &H78 - "K_F10", // &H79 - "K_F11", // &H7A - "K_F12", // &H7B - "K_F13", // &H7C - "K_F14", // &H7D - "K_F15", // &H7E - "K_F16", // &H7F - "K_F17", // &H80 - "K_F18", // &H81 - "K_F19", // &H82 - "K_F20", // &H83 - "K_F21", // &H84 - "K_F22", // &H85 - "K_F23", // &H86 - "K_F24", // &H87 - - "K_?88", // &H88 - "K_?89", // &H89 - "K_?8A", // &H8A - "K_?8B", // &H8B - "K_?8C", // &H8C - "K_?8D", // &H8D - "K_?8E", // &H8E - "K_?8F", // &H8F - - "K_NUMLOCK", // &H90 - "K_SCROLL", // &H91 - - "K_?92", // &H92 - "K_?93", // &H93 - "K_?94", // &H94 - "K_?95", // &H95 - "K_?96", // &H96 - "K_?97", // &H97 - "K_?98", // &H98 - "K_?99", // &H99 - "K_?9A", // &H9A - "K_?9B", // &H9B - "K_?9C", // &H9C - "K_?9D", // &H9D - "K_?9E", // &H9E - "K_?9F", // &H9F - "K_?A0", // &HA0 - "K_?A1", // &HA1 - "K_?A2", // &HA2 - "K_?A3", // &HA3 - "K_?A4", // &HA4 - "K_?A5", // &HA5 - "K_?A6", // &HA6 - "K_?A7", // &HA7 - "K_?A8", // &HA8 - "K_?A9", // &HA9 - "K_?AA", // &HAA - "K_?AB", // &HAB - "K_?AC", // &HAC - "K_?AD", // &HAD - "K_?AE", // &HAE - "K_?AF", // &HAF - "K_?B0", // &HB0 - "K_?B1", // &HB1 - "K_?B2", // &HB2 - "K_?B3", // &HB3 - "K_?B4", // &HB4 - "K_?B5", // &HB5 - "K_?B6", // &HB6 - "K_?B7", // &HB7 - "K_?B8", // &HB8 - "K_?B9", // &HB9 - - "K_COLON", // &HBA - "K_EQUAL", // &HBB - "K_COMMA", // &HBC - "K_HYPHEN", // &HBD - "K_PERIOD", // &HBE - "K_SLASH", // &HBF - "K_BKQUOTE", // &HC0 - - "K_?C1", // &HC1 - "K_?C2", // &HC2 - "K_?C3", // &HC3 - "K_?C4", // &HC4 - "K_?C5", // &HC5 - "K_?C6", // &HC6 - "K_?C7", // &HC7 - "K_?C8", // &HC8 - "K_?C9", // &HC9 - "K_?CA", // &HCA - "K_?CB", // &HCB - "K_?CC", // &HCC - "K_?CD", // &HCD - "K_?CE", // &HCE - "K_?CF", // &HCF - "K_?D0", // &HD0 - "K_?D1", // &HD1 - "K_?D2", // &HD2 - "K_?D3", // &HD3 - "K_?D4", // &HD4 - "K_?D5", // &HD5 - "K_?D6", // &HD6 - "K_?D7", // &HD7 - "K_?D8", // &HD8 - "K_?D9", // &HD9 - "K_?DA", // &HDA - - "K_LBRKT", // &HDB - "K_BKSLASH", // &HDC - "K_RBRKT", // &HDD - "K_QUOTE", // &HDE - "K_oDF", // &HDF - "K_oE0", // &HE0 - "K_oE1", // &HE1 - "K_oE2", // &HE2 - "K_oE3", // &HE3 - "K_oE4", // &HE4 - - "K_?E5", // &HE5 - - "K_oE6", // &HE6 - - "K_?E7", // &HE7 - "K_?E8", // &HE8 - - "K_oE9", // &HE9 - "K_oEA", // &HEA - "K_oEB", // &HEB - "K_oEC", // &HEC - "K_oED", // &HED - "K_oEE", // &HEE - "K_oEF", // &HEF - "K_oF0", // &HF0 - "K_oF1", // &HF1 - "K_oF2", // &HF2 - "K_oF3", // &HF3 - "K_oF4", // &HF4 - "K_oF5", // &HF5 - - "K_?F6", // &HF6 - "K_?F7", // &HF7 - "K_?F8", // &HF8 - "K_?F9", // &HF9 - "K_?FA", // &HFA - "K_?FB", // &HFB - "K_?FC", // &HFC - "K_?FD", // &HFD - "K_?FE", // &HFE - "K_?FF" // &HFF -}; - -const struct modifier_names s_modifier_names[] = { - {"LCTRL", 0x0001}, // Left Control flag - {"RCTRL", 0x0002}, // Right Control flag - {"LALT", 0x0004}, // Left Alt flag - {"RALT", 0x0008}, // Right Alt flag - {"SHIFT", 0x0010}, // Either shift flag - {"CTRL-do-not-use", 0x0020}, // Either ctrl flag -- don't use this for inputs - {"ALT-do-not-use", 0x0040}, // Either alt flag -- don't use this for inputs - {"CAPS", 0x0100}, // Caps lock on - {"NCAPS", 0x0200}, // Caps lock NOT on - {"NUMLOCK", 0x0400}, // Num lock on - {"NNUMLOCK", 0x0800}, // Num lock NOT on - {"SCROLL", 0x1000}, // Scroll lock on - {"NSCROLL", 0x2000}, // Scroll lock NOT on - {NULL, 0} -}; - } // namespace kmx } // namespace kbp } // namespace km diff --git a/core/src/kmx/kmx_debug.cpp b/core/src/kmx/kmx_debug.cpp deleted file mode 100644 index fcff8f272da..00000000000 --- a/core/src/kmx/kmx_debug.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - Copyright: Copyright (C) 2003-2018 SIL International. - Authors: mcdurdin -*/ -#include "kmx_processevent.h" -#include -#include - -using namespace km::kbp; -using namespace kmx; - -#define TAB "\t" -#define NL "\n" - -#include - -/** - * \def MEDIUM_BUFFER_SIZE not too big, not too small - */ - #define MEDIUM_BUFFER_SIZE (128 * 7) - -#ifdef _MSC_VER -#define _USE_WINDOWS -#endif - -#ifdef _USE_WINDOWS -#define DECLSPEC_IMPORT __declspec(dllimport) -#define WINBASEAPI DECLSPEC_IMPORT -#define VOID void -#define WINAPI __stdcall - -extern "C" -__declspec(dllimport) -void -__stdcall -OutputDebugStringA( - char *lpOutputString -); - -#else - #include -#endif - -// simulation of Windows GetTickCount() -unsigned long -GetTickCount() -{ - using namespace std::chrono; - return (unsigned long) duration_cast(steady_clock::now().time_since_epoch()).count(); -} - -int km::kbp::kmx::DebugLog_1(const char *file, int line, const char *function, const char *fmt, ...) -{ - char fmtbuf[256]; - - va_list vars; - va_start(vars, fmt); - vsnprintf(fmtbuf, sizeof(fmtbuf) / sizeof(fmtbuf[0]), fmt, vars); // I2248 // I3547 - fmtbuf[255] = 0; - va_end(vars); - - if (!g_debug_KeymanLog) - return 0; - - char windowinfo[1024]; - snprintf(windowinfo, 1024, - "%ld" TAB //"TickCount" TAB - "%s:%d" TAB //"SourceFile" TAB - "%s" TAB //"Function" - "%s" NL, //"Message" - - GetTickCount(), //"TickCount" TAB - file, line, //"SourceFile" TAB - function, //"Function" TAB - fmtbuf); //"Message" - - if (g_debug_ToConsole) { // I3951 - std::cout << windowinfo << std::endl; // OutputDebugStringA(windowinfo); - } else { -#ifdef _USE_WINDOWS - std::cout << windowinfo << std::endl; // OutputDebugStringA(windowinfo); -#else - syslog(LOG_DEBUG, "%s", windowinfo); -#endif - } - - return 0; -} - -const char *km::kbp::kmx::Debug_ModifierName(KMX_UINT modifiers) { -#ifdef _MSC_VER - __declspec(thread) -#endif - static char buf[256]; - buf[0] = 0; - for(int i = 0; s_modifier_names[i].name; i++) - if (modifiers & s_modifier_names[i].modifier) { - strcat(buf, " "); - strcat(buf, s_modifier_names[i].name); - } - - if (*buf) return buf + 1; - return "Unmodified"; -} - -const char *km::kbp::kmx::Debug_VirtualKey(KMX_WORD vk) { -#ifdef _MSC_VER - __declspec(thread) -#endif - static char buf[256]; - if (!ShouldDebug()) { - return ""; - } - - if (vk < 256) { - snprintf(buf, 256, "['%s' 0x%x]", s_key_names[vk], vk); - } - else { - snprintf(buf, 256, "[0x%x]", vk); - } - return buf; -} - -const char *km::kbp::kmx::Debug_UnicodeString(PKMX_WCHAR s, int x) { - if (!ShouldDebug()) { - return ""; - } -#ifdef _MSC_VER - __declspec(thread) -#endif - static char bufout[2][128 * 7]; - KMX_WCHAR *p; - char *q; - bufout[x][0] = 0; - for (p = s, q = bufout[x]; *p && (p - s < 128); p++) - { - snprintf(q, MEDIUM_BUFFER_SIZE, "U+%4.4X ", *p); - q = strchr(q, 0); - } - //WideCharToMultiByte(CP_ACP, 0, buf, -1, bufout, 128, NULL, NULL); - return bufout[x]; -} - -const char *km::kbp::kmx::Debug_UnicodeString(std::u16string s, int x) { - if (!ShouldDebug()) { - return ""; - } -#ifdef _MSC_VER - __declspec(thread) -#endif - static char bufout[2][128 * 7]; - auto p = s.begin(); - char *q; - bufout[x][0] = 0; - for (q = bufout[x]; (intptr_t)(q-bufout[x]) < (128*7) && p != s.end(); p++) - { - snprintf(q, MEDIUM_BUFFER_SIZE, "U+%4.4X ", *p); q = strchr(q, 0); - } - return bufout[x]; -} - -void km::kbp::kmx::write_console(KMX_BOOL error, const wchar_t *fmt, ...) { - if (!g_silent || error) { - va_list vars; - va_start(vars, fmt); - vwprintf(fmt, vars); - va_end(vars); - } -} diff --git a/core/src/kmx/kmx_file.cpp b/core/src/kmx/kmx_file.cpp index defdc63e781..527b7d5c3a2 100644 --- a/core/src/kmx/kmx_file.cpp +++ b/core/src/kmx/kmx_file.cpp @@ -4,6 +4,7 @@ */ #include "kmx_processevent.h" #include +#include "kmx_file_validator.hpp" using namespace km::kbp; using namespace kmx; @@ -50,61 +51,7 @@ const int km::kbp::kmx::CODE__SIZE[] = { // Ensure that all CODE_### sizes are defined static_assert(sizeof(CODE__SIZE) / sizeof(CODE__SIZE[0]) == (CODE_LASTCODE + 1), "Size of array CODE__SIZE not correct"); -const unsigned long CRCTable[256] = { - 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, - 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, - 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, - 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, - 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, - 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, - 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, - 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, - 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, - 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, - 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, - 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, - 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, - 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, - 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, - 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, - 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, - 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, - 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, - 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, - 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, - 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, - 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, - 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, - 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, - 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, - 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, - 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, - 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, - 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, - 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, - 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d -}; -/* - * This routine calculates the CRC for a block of data using the - * table lookup method. It accepts an original value for the crc, - * and returns the updated value. - */ - -static unsigned long CalculateBufferCRC(size_t count, KMX_BYTE *p) -{ - unsigned long temp1; - unsigned long temp2; - unsigned long crc = 0xFFFFFFFFL; - while (count-- != 0) - { - temp1 = ( crc >> 8 ) & 0x00FFFFFFL; - temp2 = CRCTable[((int) crc ^ *p++) & 0xff]; - crc = temp1 ^ temp2; - } - - return crc; -} KMX_BOOL KMX_ProcessEvent::LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKeyboard) { @@ -346,48 +293,53 @@ LPKEYBOARD KMX_ProcessEvent::FixupKeyboard(PKMX_BYTE bufp, PKMX_BYTE base) #endif -KMX_BOOL KMX_ProcessEvent::VerifyChecksum(PKMX_BYTE buf, size_t sz) -{ - KMX_DWORD tempcs; - PCOMP_KEYBOARD ckbp; - ckbp = (PCOMP_KEYBOARD) buf; - - tempcs = ckbp->dwCheckSum; - ckbp->dwCheckSum = 0; +KMX_BOOL KMX_ProcessEvent::VerifyKeyboard(PKMX_BYTE filebase, size_t sz) +{ + KMX_FileValidator *ckbp = reinterpret_cast(filebase); - return tempcs == CalculateBufferCRC(sz, buf); + return ckbp->VerifyKeyboard(sz); } -KMX_BOOL KMX_ProcessEvent::VerifyKeyboard(PKMX_BYTE filebase, size_t sz) + +KMX_BOOL KMX_FileValidator::VerifyKeyboard(std::size_t _kmn_unused(sz)) const { KMX_DWORD i; - PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) filebase; PCOMP_STORE csp; + const PKMX_BYTE filebase = (KMX_BYTE*)this; /* Check file version */ - if(ckbp->dwFileVersion < VERSION_MIN || - ckbp->dwFileVersion > VERSION_MAX) + if(dwFileVersion < VERSION_MIN || + dwFileVersion > VERSION_MAX) { /* Old or new version -- identify the desired program version */ - if(VerifyChecksum(filebase, sz)) - { - for(csp = (PCOMP_STORE)(filebase + ckbp->dpStoreArray), i = 0; i < ckbp->cxStoreArray; i++, csp++) - if(csp->dwSystemID == TSS_COMPILEDVERSION) - { - if(csp->dpString == 0) - DebugLog("errWrongFileVersion:NULL"); - else - DebugLog("errWrongFileVersion:%10.10ls", StringOffset(filebase, csp->dpString)); - return FALSE; - } + for(csp = (PCOMP_STORE)(filebase + dpStoreArray), i = 0; i < cxStoreArray; i++, csp++) { + if(csp->dwSystemID == TSS_COMPILEDVERSION) + { + if(csp->dpString == 0) + DebugLog("errWrongFileVersion:NULL"); + else + DebugLog("errWrongFileVersion:%10.10ls", KMX_ProcessEvent::StringOffset(filebase, csp->dpString)); + return FALSE; + } } DebugLog("errWrongFileVersion"); return FALSE; } - if(!VerifyChecksum(filebase, sz)) { DebugLog("errBadChecksum"); return FALSE; } + // Verify file structure + + if(StartGroup[0] != 0xFFFFFFFF && StartGroup[0] >= cxGroupArray) { + DebugLog("Invalid ANSI start group index"); + return FALSE; + } + if(StartGroup[1] != 0xFFFFFFFF && StartGroup[1] >= cxGroupArray) { + DebugLog("Invalid Unicode start group index"); + return FALSE; + } + + // TODO: verify many other offsets such as stores, groups, keys, strings! return TRUE; } diff --git a/core/src/kmx/kmx_file_validator.hpp b/core/src/kmx/kmx_file_validator.hpp new file mode 100644 index 00000000000..0f6582ad2c9 --- /dev/null +++ b/core/src/kmx/kmx_file_validator.hpp @@ -0,0 +1,34 @@ +#pragma once + +#include + +#include + +#include + +#ifdef KMN_KBP +// TODO: move this to a common namespace keyman::common::kmx_file or similar in the future +namespace km { +namespace kbp { +namespace kmx { +#endif + + +class KMX_FileValidator : public COMP_KEYBOARD { +public: + /** + * @brief Return TRUE if keyboard is OK, otherwise FALSE + * Non const because the checksum gets cleared + * + * @param sz total size of keyboard structure + * @return KMX_BOOL + */ + KMX_BOOL VerifyKeyboard(std::size_t sz) const; +}; + + +#ifdef KMN_KBP +} // namespace kmx +} // namespace kbp +} // namespace km +#endif diff --git a/core/src/kmx/kmx_plus.cpp b/core/src/kmx/kmx_plus.cpp new file mode 100644 index 00000000000..74725ba6ddf --- /dev/null +++ b/core/src/kmx/kmx_plus.cpp @@ -0,0 +1,907 @@ +/* + Copyright: Copyright (C) 2022 SIL International. + Authors: srl295 + Implementation for the KMX Plus utilities +*/ + +#include +#include +#include +#include + +#include "kmx_processevent.h" // for debug functions +#include "ldml/keyboardprocessor_ldml.h" + +#include + +namespace km { +namespace kbp { +namespace kmx { + +// double check these modifier mappings +static_assert(LCTRLFLAG == LDML_KEYS_MOD_CTRLL, "LDML modifier bitfield vs. kmx_file.h #define mismatch"); +static_assert(RCTRLFLAG == LDML_KEYS_MOD_CTRLR, "LDML modifier bitfield vs. kmx_file.h #define mismatch"); +static_assert(/*K_CTRLFLAG*/ (LCTRLFLAG|RCTRLFLAG) == LDML_KEYS_MOD_CTRL, "LDML modifier bitfield vs. kmx_file.h #define mismatch"); +static_assert(RALTFLAG == LDML_KEYS_MOD_ALTR, "LDML modifier bitfield vs. kmx_file.h #define mismatch"); +static_assert(LALTFLAG == LDML_KEYS_MOD_ALTL, "LDML modifier bitfield vs. kmx_file.h #define mismatch"); +static_assert(/*K_ALTFLAG*/ (RALTFLAG|LALTFLAG) == LDML_KEYS_MOD_ALT, "LDML modifier bitfield vs. kmx_file.h #define mismatch"); +static_assert(CAPITALFLAG == LDML_KEYS_MOD_CAPS, "LDML modifier bitfield vs. kmx_file.h #define mismatch"); +static_assert(K_SHIFTFLAG == LDML_KEYS_MOD_SHIFT, "LDML modifier bitfield vs. kmx_file.h #define mismatch"); // "either" shift + +/** + * \def LDML_IS_VALID_MODIFIER_BITS test whether x is a valid modifier bitfield + * i.e. contains no bits outside of LDML_KEYS_MOD_ALL + */ +#define LDML_IS_VALID_MODIFIER_BITS(x) ((LDML_KEYS_MOD_ALL & x) == x) + +static_assert(LDML_IS_VALID_MODIFIER_BITS(0), "LDML_IS_VALID_MODIFIER_BITS test"); +static_assert(!LDML_IS_VALID_MODIFIER_BITS(0xFFFFFF), "LDML_IS_VALID_MODIFIER_BITS(0xFFFFFF) should be 0"); +static_assert(LDML_IS_VALID_MODIFIER_BITS(LDML_KEYS_MOD_CAPS), "LDML_IS_VALID_MODIFIER_BITS test"); + +/** + * \def _DEBUG_IDENT_SAFE for use by DEBUG_IDENT + */ +#define _DEBUG_IDENT_SAFE(x,b) (((x)>>b)&0x7F)<0x20?'?':(((x)>>b)&0x7F) + +/** + * \def DEBUG_IDENT + * Expands to four chars: a,b,c,d + * for purposes of debug logging a hex identifier + */ +#define DEBUG_IDENT(x) \ + _DEBUG_IDENT_SAFE(x,0), \ + _DEBUG_IDENT_SAFE(x,8), \ + _DEBUG_IDENT_SAFE(x,16), \ + _DEBUG_IDENT_SAFE(x,24) + +/** + * @brief Validate (and print out) a section name dword + * + * @param ident the hex dword + * @return true if valid + * @return false if invalid + */ +static bool +validate_section_name(KMX_DWORD ident) { + for (int i = 0; i < 4; i++) { + unsigned char ch = ident & 0xFF; + if (ch < 0x20 || ch > 0x7F) { + DebugLog("Invalid section name %c%c%c%c (0x%X)", DEBUG_IDENT(ident), ident); + assert(false); + return false; + } + ident >>= 8; + } + return true; +} + +/** + * @brief cast to a COMP_KMXPLUS_HEADER from bytes + * + * @param data + * @param length + * @param ident + * @return const kmx::COMP_KMXPLUS_HEADER* + */ +static const kmx::COMP_KMXPLUS_HEADER * +header_from_bytes(const uint8_t *data, KMX_DWORD length, uint32_t ident) { + if (!data) { + DebugLog("!data"); + assert(false); + return nullptr; + } + if (length < LDML_LENGTH_HEADER) { + DebugLog("length < LDML_LENGTH_HEADER"); + assert(false); + return nullptr; + } + const COMP_KMXPLUS_HEADER *all = reinterpret_cast(data); + if (!all->valid(length)) { + DebugLog("header failed validation"); + assert(false); + return nullptr; + } + if (all->ident != ident) { + DebugLog("header had wrong section id"); + assert(false); + return nullptr; + } + return all; +} + +/** + * @brief Accessor for a section based on bytes + * + * @tparam T + * @param data + * @param length + * @return const T* + */ +template +const T *section_from_bytes(const uint8_t *data, KMX_DWORD length) { + if (length < sizeof(T)) { // Does not include dynamic data. First check. + DebugLog("length < sizeof(section)"); + assert(false); + return nullptr; + } + const COMP_KMXPLUS_HEADER *header = header_from_bytes(data, length, T::IDENT); + const T *section = reinterpret_cast(header); + if (section != nullptr && section->valid(length)) { + return section; + } else { + assert(false); + return nullptr; + } +} + +template +const T *section_from_sect(const COMP_KMXPLUS_SECT* sect) { + const uint8_t *rawbytes = reinterpret_cast(sect); + if (rawbytes == nullptr) { + DebugLog("section_from_sect(nullptr) == nullptr"); + assert(false); + return nullptr; + } + KMX_DWORD offset = sect->find(T::IDENT); + if (!offset) { + DebugLog("section_from_sect() - not found. section %c%c%c%c (0x%X)", DEBUG_IDENT(T::IDENT), T::IDENT); + return nullptr; + } + KMX_DWORD entrylength = sect->total - offset; + return section_from_bytes(rawbytes+offset, entrylength); +} + +bool +COMP_KMXPLUS_HEADER::valid(KMX_DWORD length) const { + DebugLog("%c%c%c%c: (%X) size 0x%X\n", DEBUG_IDENT(ident), ident, size); + if (size < LDML_LENGTH_HEADER) { + DebugLog("size < LDML_LENGTH_HEADER"); + assert(false); + return false; + } + if (size > length) { + DebugLog("size > length"); + assert(false); + return false; + } + if (!validate_section_name(ident)) { + return false; + } + DebugLog(" (header OK)"); // newline after section name + return true; +} + +bool +COMP_KMXPLUS_LOCA::valid(KMX_DWORD _kmn_unused(length)) const { + if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { + DebugLog("header.size < expected size"); + assert(false); + return false; + } + for(KMX_DWORD i=0; i0x%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); + if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { + DebugLog("header.size < expected size"); + assert(false); + return false; + } + if (baseCharacter != 0) { + DebugLog("disp: baseCharacter str#0x%X", baseCharacter); + } + for (KMX_DWORD i=0; i str0x%X", i, entries[i].to, entries[i].display); + if (entries[i].to == 0 || entries[i].display == 0) { + DebugLog("disp to: or display: has a zero string"); + assert(false); + return false; + } + } + return true; +} + +bool +COMP_KMXPLUS_STRS::valid(KMX_DWORD _kmn_unused(length)) const { + DebugLog("strs: 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 header.size) { + DebugLog("#0x%X: expected end of string past header.size", i); + assert(false); + return false; + } + const uint8_t* thisptr = reinterpret_cast(this); + const KMX_WCHAR* start = reinterpret_cast(thisptr+offset); + if(start[length] != 0) { + DebugLog("#0x%X: String of length 0x%x not null terminated", i, length); + assert(start[length] == 0); + return false; + } + // TODO-LDML: validate valid UTF-16LE? + DebugLog("#0x%X: '%s'", i, Debug_UnicodeString(start)); + } + return true; +} + +bool +COMP_KMXPLUS_SECT::valid(KMX_DWORD length) const { + DebugLog("sect: total 0x%X\n", total); + DebugLog("sect: count 0x%X\n", count); + if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { + DebugLog("header.size < expected size"); + assert(false); + return false; + } + + // now validate each component + bool overall_valid = true; + for (KMX_DWORD i = 0; i < count; i++) { + const COMP_KMXPLUS_SECT_ENTRY& entry = entries[i]; + DebugLog("%c%c%c%c #%d: %X @ %X\n", DEBUG_IDENT(entry.sect), i, entry.sect, entry.offset); + if(!validate_section_name(entry.sect)) { + return false; + } + if (entry.sect == LDML_SECTIONID_SECT) { + DebugLog("Invalid nested 'sect'"); + overall_valid = false; + continue; + } + const uint8_t* data = reinterpret_cast(this); + const uint8_t* entrydata = data + entry.offset; + KMX_DWORD entrylength = length - entry.offset; + // just validate header + if(header_from_bytes(entrydata, entrylength, entry.sect) == nullptr) { + DebugLog("Invalid header %X", entry.sect); + assert(false); + overall_valid = false; + continue; + } + } + return overall_valid; +} + +// ---- transform related fcns +bool +COMP_KMXPLUS_ELEM::valid(KMX_DWORD _kmn_unused(length)) const { + if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { + DebugLog("header.size 0x%X < expected size"); + return false; + } + const COMP_KMXPLUS_ELEM_ENTRY &firstEntry = entries[0]; + if (firstEntry.length != 0 || firstEntry.offset != 0) { + DebugLog("ERROR: elem[0].length=0x%x, elem[0].offset=0x%x, both should be zero", + firstEntry.length, firstEntry.offset); + return false; + } + for (KMX_DWORD e = 1; e < count; e++) { + // Don't need to recheck the first entry hbere. + KMX_DWORD listLength; + if (getElementList(e, listLength) == nullptr) { + return false; + } + if (listLength == 0) { // only the first element should have length zero + DebugLog("ERROR: elem[0x%x].length == 0", e); + return false; + } + } + return true; +} + +const COMP_KMXPLUS_ELEM_ELEMENT * +COMP_KMXPLUS_ELEM::getElementList(KMX_DWORD elementNumber, KMX_DWORD &length) const { + if (elementNumber >= count) { + DebugLog("ERROR: COMP_KMXPLUS_ELEM::getElementList(%d) >= count %d", elementNumber, count); + assert(false); + return nullptr; + } + const COMP_KMXPLUS_ELEM_ENTRY &entry = entries[elementNumber]; + length = entry.length; + if (length == 0) { + return nullptr; // Normal case for first element + } + if (entry.offset + (entry.length * sizeof(COMP_KMXPLUS_ELEM_ELEMENT)) > header.size) { + DebugLog("ERROR: !! COMP_KMXPLUS_ELEM::getElementList(%d) would be off end of data area", elementNumber); + assert(false); + return nullptr; + } + // pointer to beginning of elem section + const uint8_t *rawdata = reinterpret_cast(this); + // pointer to specified entry + return reinterpret_cast(rawdata + entry.offset); +} + +std::u16string +COMP_KMXPLUS_ELEM_ELEMENT::get_string() const { + assert(!(flags & LDML_ELEM_FLAGS_UNICODE_SET)); // should not be called. + char16_single buf; + const int len = Utf32CharToUtf16(element, buf); + return std::u16string(buf.ch, len); +} + +bool +COMP_KMXPLUS_TRAN::valid(KMX_DWORD _kmn_unused(length)) const { + if (header.size < sizeof(*this)+(sizeof(entries[0])*count)) { + DebugLog("header.size < expected size"); + assert(false); + return false; + } + // TODO-LDML + DebugLog("!! More to do here."); + return true; +} + +bool +COMP_KMXPLUS_LAYR::valid(KMX_DWORD _kmn_unused(length)) const { + if (header.size < sizeof(*this) + + (listCount * sizeof(COMP_KMXPLUS_LAYR_LIST)) + + (layerCount * sizeof(COMP_KMXPLUS_LAYR_ENTRY)) + + (rowCount * sizeof(COMP_KMXPLUS_LAYR_ROW)) + + (keyCount * sizeof(COMP_KMXPLUS_LAYR_KEY))) { + DebugLog("header.size < expected size"); + assert(false); + return false; + } + DebugLog("layr header is valid"); + // Note: We only do minimal validation here because of the + // dynamic structure. See COMP_KMXPLUS_LAYR_Helper.setLayr() (below) + // all remaining checks + return true; +} + +COMP_KMXPLUS_LAYR_Helper::COMP_KMXPLUS_LAYR_Helper() : layr(nullptr), is_valid(false) { +} + +bool +COMP_KMXPLUS_LAYR_Helper::setLayr(const COMP_KMXPLUS_LAYR *newLayr) { + DebugLog("validating newLayr=%p", newLayr); + is_valid = true; + if (newLayr == nullptr) { + // null = invalid + is_valid = false; + // No assert here: just a missing layer + return false; + } + layr = newLayr; + const uint8_t *rawdata = reinterpret_cast(newLayr); + rawdata += LDML_LENGTH_LAYR; // skip past non-dynamic portion + // lists + if (layr->listCount > 0) { + lists = reinterpret_cast(rawdata); + } else { + lists = nullptr; + is_valid = false; + assert(is_valid); + } + rawdata += sizeof(COMP_KMXPLUS_LAYR_LIST) * layr->listCount; + // entries + if (layr->layerCount > 0) { + entries = reinterpret_cast(rawdata); + } else { + entries = nullptr; + is_valid = false; + assert(is_valid); + } + rawdata += sizeof(COMP_KMXPLUS_LAYR_ENTRY) * layr->layerCount; + // rows + if (layr->rowCount > 0) { + rows = reinterpret_cast(rawdata); + } else { + rows = nullptr; + is_valid = false; + assert(is_valid); + } + rawdata += sizeof(COMP_KMXPLUS_LAYR_ROW) * layr->rowCount; + // keys + if (layr->keyCount > 0) { + keys = reinterpret_cast(rawdata); + } else { + keys = nullptr; + is_valid = false; + assert(is_valid); + } + + // Now, validate offsets by walking + if (is_valid) { + for(KMX_DWORD i = 0; is_valid && i < layr->listCount; i++) { + const COMP_KMXPLUS_LAYR_LIST &list = lists[i]; + // is the count off the end? + DebugLog( + " %d: hardware s#0x%X, layers %d..%d, minDeviceWidth %.1fmm", i, list.hardware, list.layer, + list.layer + list.count - 1, list.minDeviceWidth * (double)0.1); + if ((list.layer >= layr->layerCount) || (list.layer + list.count > layr->layerCount)) { + DebugLog("COMP_KMXPLUS_LAYR_Helper: list[%d] would access layer %d+%d, > count %d", + i, list.layer, list.count, layr->layerCount); + is_valid = false; + assert(is_valid); + } + } + for(KMX_DWORD i = 0; is_valid && i < layr->layerCount; i++) { + const COMP_KMXPLUS_LAYR_ENTRY &entry = entries[i]; + // is the count off the end? + DebugLog( + " %d: id s#0x%X, rows %d..%d, modifier=0x%X", i, entry.id, entry.row, entry.row+entry.count-1, entry.mod); + if ((entry.row >= layr->rowCount) || (entry.row + entry.count > layr->rowCount)) { + DebugLog("COMP_KMXPLUS_LAYR_Helper: entry[%d] would access row %d+%d, > count %d", + i, entry.row, entry.count, layr->rowCount); + is_valid = false; + assert(is_valid); + } + if (!LDML_IS_VALID_MODIFIER_BITS(entry.mod)) { + DebugLog("Invalid modifier value"); + assert(false); + return false; + } + } + for(KMX_DWORD i = 0; is_valid && i < layr->rowCount; i++) { + const COMP_KMXPLUS_LAYR_ROW &row = rows[i]; + // is the count off the end? + if ((row.key >= layr->keyCount) || (row.key + row.count > layr->keyCount)) { + DebugLog("COMP_KMXPLUS_LAYR_Helper: row[%d] would access key %d+%d, > count %d", + i, row.key, row.count, layr->keyCount); + is_valid = false; + assert(is_valid); + } + } + } + // Return results + DebugLog("COMP_KMXPLUS_LAYR_Helper.setLayr(): %s", is_valid ? "valid" : "invalid"); + assert(is_valid); + return is_valid; +} + +bool COMP_KMXPLUS_LAYR_Helper::valid() const { + return is_valid; +} + +const COMP_KMXPLUS_LAYR_LIST * +COMP_KMXPLUS_LAYR_Helper::getList(KMX_DWORD list) const { + if (!valid() || list >= layr->listCount) { + assert(false); + return nullptr; + } + return lists + list; +} + +const COMP_KMXPLUS_LAYR_ENTRY * +COMP_KMXPLUS_LAYR_Helper::getEntry(KMX_DWORD entry) const { + if (!valid() || entry >= layr->layerCount) { + assert(false); + return nullptr; + } + return entries + entry; +} + +const COMP_KMXPLUS_LAYR_ROW * +COMP_KMXPLUS_LAYR_Helper::getRow(KMX_DWORD row) const { + if (!valid() || row >= layr->rowCount) { + assert(false); + return nullptr; + } + return rows + row; +} + +const COMP_KMXPLUS_LAYR_KEY * +COMP_KMXPLUS_LAYR_Helper::getKey(KMX_DWORD key) const { + if (!valid() || key >= layr->keyCount) { + assert(false); + return nullptr; + } + return keys + key; +} + +bool +COMP_KMXPLUS_KEYS::valid(KMX_DWORD _kmn_unused(length)) const { + if (header.size < sizeof(*this) + + (keyCount * sizeof(COMP_KMXPLUS_KEYS_KEY)) + + (flicksCount * sizeof(COMP_KMXPLUS_KEYS_FLICK_LIST)) + + (flickCount * sizeof(COMP_KMXPLUS_KEYS_FLICK_ELEMENT)) + + (kmapCount * sizeof(COMP_KMXPLUS_KEYS_KMAP))) { + DebugLog("header.size < expected size"); + assert(false); + return false; + } + // further validation in the COMP_KMXPLUS_KEYS_Helper helper obj + return true; +} + + +COMP_KMXPLUS_KEYS_Helper::COMP_KMXPLUS_KEYS_Helper() : key2(nullptr), is_valid(false) { +} + +bool +COMP_KMXPLUS_KEYS_Helper::setKeys(const COMP_KMXPLUS_KEYS *newKeys) { + DebugLog("validating newKeys=%p", newKeys); + is_valid = true; + if (newKeys == nullptr) { + // null = invalid + is_valid = false; + // No assert here: just a missing layer + return false; + } + key2 = newKeys; + const uint8_t *rawdata = reinterpret_cast(newKeys); + rawdata += LDML_LENGTH_KEYS; // skip past non-dynamic portion + // keys + if (key2->keyCount > 0) { + keys = reinterpret_cast(rawdata); + } else { + keys = nullptr; + is_valid = false; + assert(is_valid); + } + rawdata += sizeof(COMP_KMXPLUS_KEYS_KEY) * key2->keyCount; + // flicks + if (key2->flicksCount > 0) { + flickLists = reinterpret_cast(rawdata); + } else { + flickLists = nullptr; // not an error + } + rawdata += sizeof(COMP_KMXPLUS_KEYS_FLICK_LIST) * key2->flicksCount; + // flick + if (key2->flickCount > 0) { + flickElements = reinterpret_cast(rawdata); + } else { + flickElements = nullptr; // not an error + } + rawdata += sizeof(COMP_KMXPLUS_KEYS_FLICK_ELEMENT) * key2->flickCount; + // kmap + if (key2->kmapCount > 0) { + kmap = reinterpret_cast(rawdata); + } else { + kmap = nullptr; // not an error + } + + // Now, validate offsets by walking + if (is_valid) { + for(KMX_DWORD i = 0; is_valid && i < key2->keyCount; i++) { + const auto &key = keys[i]; + // is the count off the end? + DebugLog( " id=0x%X, to=0x%X, flicks=%d", i, key.id, key.to, key.flicks); // TODO-LDML: could dump more fields here + if (key.flicks >0 && key.flicks >= key2->flicksCount) { + DebugLog("key[%d] has invalid flicks index %d", i, key.flicks); + is_valid = false; + assert(is_valid); + } + } + for(KMX_DWORD i = 0; is_valid && i < key2->flicksCount; i++) { + const auto &e = flickLists[i]; + // is the count off the end? + DebugLog(" %d: index %d, count %d", i, e.flick, e.count); + if (i == 0) { + if (e.flick != 0 || e.count != 0) { + DebugLog("Error: Invalid Flick #0"); + is_valid = false; + assert(is_valid); + } + } else if ((e.flick >= key2->flickCount) || (e.flick + e.count > key2->flickCount)) { + DebugLog("flicks[%d] would access flick %d+%d, > count %d", i, e.flick, e.count, key2->flickCount); + is_valid = false; + assert(is_valid); + } + } + for(KMX_DWORD i = 0; is_valid && i < key2->flickCount; i++) { + const auto &e = flickElements[i]; + // is the count off the end? + DebugLog(" %d: to=0x%X, directions=0x%X, flags=0x%X", i, e.to, e.directions, e.flags); + } + // now the kmap + DebugLog(" kmap count: #0x%X\n", key2->kmapCount); + for (KMX_DWORD i = 0; i < key2->kmapCount; i++) { + DebugLog(" #0x%d\n", i); + auto &entry = kmap[i]; + DebugLog(" vkey\t0x%X\n", entry.vkey); + DebugLog(" mod\t0x%X\n", entry.mod); + DebugLog(" key\t#0x%X\n", entry.key); + if (!LDML_IS_VALID_MODIFIER_BITS(entry.mod)) { + DebugLog("Invalid modifier value"); + assert(false); + is_valid = false; + } + if (entry.key >= key2->keyCount) { + // preposterous key # + DebugLog("kmap[0x%X].key = #0x%X, but that is >= keyCount 0x%X", i, entry.key, key2->keyCount); + assert(false); + is_valid = false; + } + } + } + // Return results + DebugLog("COMP_KMXPLUS_KEYS_Helper.setKeys(): %s", is_valid ? "valid" : "invalid"); + assert(is_valid); + return is_valid; +} + +const COMP_KMXPLUS_KEYS_KEY * +COMP_KMXPLUS_KEYS_Helper::getKeys(KMX_DWORD i) const { + if (!valid() || i >= key2->keyCount) { + assert(false); + return nullptr; + } + return keys + i; +} + +const COMP_KMXPLUS_KEYS_KEY* +COMP_KMXPLUS_KEYS_Helper::findKeyByStringId(KMX_DWORD strId, KMX_DWORD &i) const { + for (i = 0; i < key2->keyCount; i++) { + if (keys[i].id == strId) { + return &keys[i]; + } + } + return nullptr; +} + +const COMP_KMXPLUS_KEYS_FLICK_LIST * +COMP_KMXPLUS_KEYS_Helper::getFlickLists(KMX_DWORD i) const { + if (!valid() || i >= key2->flicksCount) { + assert(false); + return nullptr; + } + return flickLists + i; +} + +const COMP_KMXPLUS_KEYS_FLICK_ELEMENT * +COMP_KMXPLUS_KEYS_Helper::getFlickElements(KMX_DWORD i) const { + if (!valid() || i >= key2->flickCount) { + assert(false); + return nullptr; + } + return flickElements + i; +} + +const COMP_KMXPLUS_KEYS_KMAP * +COMP_KMXPLUS_KEYS_Helper::getKmap(KMX_DWORD i) const { + if (!valid() || i >= key2->kmapCount) { + assert(false); + return nullptr; + } + return kmap + i; +} + +std::u16string +COMP_KMXPLUS_KEYS_KEY::get_string() const { + assert(!(flags & LDML_KEYS_KEY_FLAGS_EXTEND)); // should not be called. + char16_single buf; + const int len = Utf32CharToUtf16(to, buf); + return std::u16string(buf.ch, len); +} + +// LIST + +bool +COMP_KMXPLUS_LIST::valid(KMX_DWORD _kmn_unused(length)) const { + if (header.size < sizeof(*this) + + (listCount * sizeof(COMP_KMXPLUS_LIST_ITEM)) + + (indexCount * sizeof(COMP_KMXPLUS_LIST_INDEX))) { + DebugLog("header.size < expected size"); + assert(false); + return false; + } + // TODO-LDML: further validation in the COMP_KMXPLUS_LIST_Helper class + return true; +} + + +COMP_KMXPLUS_LIST_Helper::COMP_KMXPLUS_LIST_Helper() : list(nullptr), is_valid(false) { +} + +bool +COMP_KMXPLUS_LIST_Helper::setList(const COMP_KMXPLUS_LIST *newList) { + DebugLog("validating newList=%p", newList); + is_valid = true; + if (newList == nullptr) { + // null = invalid + is_valid = false; + // No assert here: just a missing layer + return false; + } + list = newList; + const uint8_t *rawdata = reinterpret_cast(newList); + rawdata += LDML_LENGTH_LIST; // skip past non-dynamic portion + // lists + if (list->listCount > 0) { + lists = reinterpret_cast(rawdata); + } else { + lists = nullptr; + // not invalid, just empty. + } + rawdata += sizeof(COMP_KMXPLUS_LIST_ITEM) * list->listCount; + // entries + if (list->indexCount > 0) { + indices = reinterpret_cast(rawdata); + } else { + indices = nullptr; + } + // rawdata += sizeof(COMP_KMXPLUS_LIST_INDEX) * list->indexCount; + + // Now, validate offsets by walking + if (is_valid) { + for (KMX_DWORD i = 0; is_valid && i < list->listCount; i++) { + const auto &e = lists[i]; + // is the count off the end? + DebugLog("list 0x%X: index %d, count %d", i, e.index, e.count); + if (i == 0) { + if (e.index != 0 || e.count != 0) { + DebugLog("Error: Invalid List #0"); + is_valid = false; + assert(is_valid); + } + } else if ((e.index >= list->indexCount) || (e.index + e.count > list->indexCount)) { + DebugLog("list[%d] would access index %d+%d, > count %d", i, e.index, e.count, list->indexCount); + is_valid = false; + assert(is_valid); + } + } + for (KMX_DWORD i = 0; is_valid && i < list->indexCount; i++) { + const auto &e = indices[i]; + DebugLog(" index %d: str 0x%X", i, e); + } + } + // Return results + DebugLog("COMP_KMXPLUS_LIST_Helper.setList(): %s", is_valid ? "valid" : "invalid"); + assert(is_valid); + return is_valid; +} + +const COMP_KMXPLUS_LIST_ITEM * +COMP_KMXPLUS_LIST_Helper::getList(KMX_DWORD i) const { + if (!valid() || i >= list->listCount) { + assert(false); + return nullptr; + } + return lists + i; +} + +const COMP_KMXPLUS_LIST_INDEX * +COMP_KMXPLUS_LIST_Helper::getIndex(KMX_DWORD i) const { + if (!valid() || i >= list->indexCount) { + assert(false); + return nullptr; + } + return indices + i; +} + +// ---- constructor + +kmx_plus::kmx_plus(const COMP_KEYBOARD *keyboard, size_t length) + : loca(nullptr), meta(nullptr), + sect(nullptr), strs(nullptr), vkey(nullptr), valid(false) { + + DebugLog("kmx_plus(): Got a COMP_KEYBOARD at %p\n", keyboard); + if (!(keyboard->dwFlags & KF_KMXPLUS)) { + DebugLog("Err: flags COMP_KEYBOARD.dwFlags did not have KF_KMXPLUS set"); + valid = false; + assert(valid); + return; + } + const COMP_KEYBOARD_EX* ex = reinterpret_cast(keyboard); + + DebugLog("kmx_plus(): KMXPlus offset 0x%X, KMXPlus size 0x%X\n", ex->kmxplus.dpKMXPlus, ex->kmxplus.dwKMXPlusSize); + if (ex->kmxplus.dpKMXPlus < sizeof(kmx::COMP_KEYBOARD_EX)) { + DebugLog("dwKMXPlus is not past the end of COMP_KEYBOARD_EX"); + valid = false; + assert(valid); + return; + } + if ( ex->kmxplus.dpKMXPlus + ex->kmxplus.dwKMXPlusSize > length) { + DebugLog("dpKMXPlus + dwKMXPlusSize is past the end of the file"); + valid = false; + assert(valid); + return; + } + + const uint8_t* rawdata = reinterpret_cast(keyboard); + + sect = section_from_bytes(rawdata+ex->kmxplus.dpKMXPlus, ex->kmxplus.dwKMXPlusSize); + if (sect == nullptr) { + DebugLog("kmx_plus(): 'sect' did not validate"); + valid = false; + } else { + valid = true; + // load other sections, validating as we go + // these will be nullptr if they don't validate + disp = section_from_sect(sect); + elem = section_from_sect(sect); + key2 = section_from_sect(sect); + layr = section_from_sect(sect); + list = section_from_sect(sect); + loca = section_from_sect(sect); + meta = section_from_sect(sect); + strs = section_from_sect(sect); + tran = section_from_sect(sect); + vkey = section_from_sect(sect); + + // calculate and validate the dynamic parts + (void)key2Helper.setKeys(key2); + (void)layrHelper.setLayr(layr); + (void)listHelper.setList(list); + } +} + +KMX_DWORD COMP_KMXPLUS_SECT::find(KMX_DWORD ident) const { + for (KMX_DWORD i = 0; i < count; i++) { + if (ident == entries[i].sect) { + return entries[i].offset; + } + } + return 0; +} + +std::u16string +COMP_KMXPLUS_STRS::get(KMX_DWORD entry) const { + assert(entry < count); + if (entry >= count) { + return std::u16string(); // Fallback: empty string + } + const KMX_DWORD offset = entries[entry].offset; + const KMX_DWORD length = entries[entry].length; + assert(offset+((length+1)*2) <= header.size); // assert not out of bounds + const uint8_t* thisptr = reinterpret_cast(this); + const KMX_WCHAR* start = reinterpret_cast(thisptr+offset); + return std::u16string(start, length); +} + +KMX_DWORD COMP_KMXPLUS_STRS::find(const std::u16string& s) const { + if (s.empty()) { + return 0; // shortcut + } + // TODO-LDML: You're not going to search these linearly, reinterpreting each time??! + for (KMX_DWORD i = 0; i +#include +#include +#include +#include + +namespace km { +namespace kbp { +namespace kmx { + +/** + * Using C99 flexible array initializers: entries[] + * https://docs.microsoft.com/en-us/cpp/error-messages/compiler-warnings/compiler-warning-levels-2-and-4-c4200 + */ +#if defined(_WIN32) +#pragma warning ( disable : 4200 ) +#endif + +/** + * Indicates an offset into the strs table (0 = zero length) + */ +typedef KMX_DWORD KMXPLUS_STR; +/** + * Indicates an offset into the list table (0 = zero length) +*/ +typedef KMX_DWORD KMXPLUS_LIST; +/** + * Indicates an offset into the elem table (0 = zero length) +*/ +typedef KMX_DWORD KMXPLUS_ELEM; + +// forward declarations +struct COMP_KMXPLUS_TRAN_ENTRY; +struct COMP_KMXPLUS_TRAN; + +struct COMP_KMXPLUS_HEADER { + KMX_DWORD ident; // 0000 Section name + KMX_DWORD size; // 0004 Section length + bool valid(KMX_DWORD length) const; +}; + +// Assert that the length matches the declared length +static_assert(sizeof(struct COMP_KMXPLUS_HEADER) == LDML_LENGTH_HEADER, "mismatched size of section header"); + +/* ------------------------------------------------------------------ + * sect section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_SECT_ENTRY { + KMX_DWORD sect; // 0010+ Section identity + KMX_DWORD offset; // 0014+ Section offset relative to dpKMXPlus of section +}; + +struct COMP_KMXPLUS_SECT { + static const KMX_DWORD IDENT = LDML_SECTIONID_SECT; + COMP_KMXPLUS_HEADER header; + KMX_DWORD total; // 0008 KMXPlus entire length + KMX_DWORD count; // 000C number of section headers + COMP_KMXPLUS_SECT_ENTRY entries[]; // 0010 section entries + /** + * @brief Get the offset of a section, or 0 + * + * @param ident section id such as 'strs'. Never 'sect' + * @return KMX_DWORD offset from beginning of kmxplus + */ + KMX_DWORD find(KMX_DWORD ident) const; + /** + * @brief True if section is valid. + * Does not validate the entire file. + */ + bool valid(KMX_DWORD length) const; +}; + +// Assert that the length matches the declared length +static_assert(sizeof(struct COMP_KMXPLUS_SECT) == LDML_LENGTH_SECT, "mismatched size of section sect"); +static_assert(sizeof(struct COMP_KMXPLUS_SECT) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); + +/* ------------------------------------------------------------------ + * bksp section + ------------------------------------------------------------------ */ + +typedef COMP_KMXPLUS_TRAN_ENTRY COMP_KMXPLUS_BKSP_ENTRY; +typedef COMP_KMXPLUS_TRAN COMP_KMXPLUS_BKSP; + +/* ------------------------------------------------------------------ + * elem section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_ELEM_ELEMENT { + KMX_DWORD element; // str: output string or UTF-32LE codepoint + KMX_DWORD flags; // flag and order values + /** + * @brief Get the 'to' as a string, if flags&LDML_ELEM_FLAGS_UNICODE_SET is not set + * + * @return std::u16string + */ + std::u16string get_string() const; +}; + +struct COMP_KMXPLUS_ELEM_ENTRY { + KMX_DWORD offset; // 0010+ offset from this blob + KMX_DWORD length; // 0014+ str length (ELEMENT units) +}; + +struct COMP_KMXPLUS_ELEM { + static const KMX_DWORD IDENT = LDML_SECTIONID_ELEM; + COMP_KMXPLUS_HEADER header; + KMX_DWORD count; // 0008 count of str entries + COMP_KMXPLUS_ELEM_ENTRY entries[]; // 000C+ entries + + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; + /** + * @param elementNumber element number, 0..count-1 + * @param length fillin: length of list + * @return pointer to first element of list of length length. or nullptr + */ + const COMP_KMXPLUS_ELEM_ELEMENT *getElementList(KMX_DWORD elementNumber, + KMX_DWORD &length) const; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_ELEM) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_ELEM) == LDML_LENGTH_ELEM, "mismatched size of section elem"); + +/* ------------------------------------------------------------------ + * finl section + ------------------------------------------------------------------ */ + +// TODO-LDML: IDENT +typedef COMP_KMXPLUS_TRAN_ENTRY COMP_KMXPLUS_FINL_ENTRY; + +// TODO-LDML: IDENT +typedef COMP_KMXPLUS_TRAN COMP_KMXPLUS_FINL; + +/* ------------------------------------------------------------------ + * keys section is now key2.kmap + ------------------------------------------------------------------ */ + +/* ------------------------------------------------------------------ + * loca section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_LOCA_ENTRY { + KMXPLUS_STR locale; // 0010+ locale string entry +}; + +struct COMP_KMXPLUS_LOCA { + static const KMX_DWORD IDENT = LDML_SECTIONID_LOCA; + COMP_KMXPLUS_HEADER header; + KMX_DWORD count; // 0008 number of locales + COMP_KMXPLUS_LOCA_ENTRY entries[]; + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_LOCA) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_LOCA) == LDML_LENGTH_LOCA, "mismatched size of section loca"); + +/* ------------------------------------------------------------------ + * meta section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_META { + static const KMX_DWORD IDENT = LDML_SECTIONID_META; + COMP_KMXPLUS_HEADER header; + KMXPLUS_STR author; + KMXPLUS_STR conform; + KMXPLUS_STR layout; + KMXPLUS_STR normalization; + KMXPLUS_STR indicator; + KMXPLUS_STR version; + KMX_DWORD settings; + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_META) == LDML_LENGTH_META, "mismatched size of section meta"); + +/* ------------------------------------------------------------------ + * name section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_NAME_ENTRY { + KMXPLUS_STR name; +}; + +struct COMP_KMXPLUS_NAME { + static const KMX_DWORD IDENT = LDML_SECTIONID_NAME; + COMP_KMXPLUS_HEADER header; + KMX_DWORD count; + COMP_KMXPLUS_NAME_ENTRY entries[]; + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_NAME) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_NAME) == LDML_LENGTH_NAME, "mismatched size of section name"); + +/* ------------------------------------------------------------------ + * ordr section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_ORDR_ENTRY { + KMXPLUS_ELEM elements; + KMXPLUS_ELEM before; +}; + +struct COMP_KMXPLUS_ORDR { + static const KMX_DWORD IDENT = LDML_SECTIONID_ORDR; + COMP_KMXPLUS_HEADER header; + KMX_DWORD count; + COMP_KMXPLUS_ORDR_ENTRY entries[]; + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_ORDR) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_ORDR) == LDML_LENGTH_ORDR, "mismatched size of section ordr"); + +/* ------------------------------------------------------------------ + * strs section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_STRS_ENTRY { + KMX_DWORD offset; // 0010+ offset from this blob + KMX_DWORD length; // 0014+ str length (UTF-16LE units) +}; + +struct COMP_KMXPLUS_STRS { + static const KMX_DWORD IDENT = LDML_SECTIONID_STRS; + COMP_KMXPLUS_HEADER header; + KMX_DWORD count; // 0008 count of str entries + COMP_KMXPLUS_STRS_ENTRY entries[]; // 0010+ entries + + /** + * @brief Get a string entry + * + * @param entry entry number + * @param buf output buffer + * @param bufsiz buffer size in bytes + * @return nullptr or a pointer to the output buffer + */ + std::u16string get(KMX_DWORD entry) const; + + /** + * Slow search + */ + KMX_DWORD find(const std::u16string&) const; + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_STRS) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_STRS) == LDML_LENGTH_STRS, "mismatched size of section strs"); + +/* ------------------------------------------------------------------ + * tran section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_TRAN_ENTRY { + KMXPLUS_ELEM from; + KMXPLUS_STR to; + KMXPLUS_ELEM before; + KMX_DWORD flags; +}; + + +struct COMP_KMXPLUS_TRAN { + static const KMX_DWORD IDENT = LDML_SECTIONID_TRAN; + COMP_KMXPLUS_HEADER header; + KMX_DWORD count; + COMP_KMXPLUS_TRAN_ENTRY entries[]; + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_TRAN) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_TRAN) == LDML_LENGTH_TRAN, "mismatched size of section tran"); + +/* ------------------------------------------------------------------ + * vkey section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_VKEY_ENTRY { + KMX_DWORD vkey; + KMX_DWORD target; +}; + +struct COMP_KMXPLUS_VKEY { + static const KMX_DWORD IDENT = LDML_SECTIONID_VKEY; + COMP_KMXPLUS_HEADER header; + KMX_DWORD 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 + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_DISP_ENTRY { + KMX_DWORD to; + KMX_DWORD display; +}; + +struct COMP_KMXPLUS_DISP { + static const KMX_DWORD IDENT = LDML_SECTIONID_DISP; + COMP_KMXPLUS_HEADER header; + KMX_DWORD count; + KMXPLUS_STR baseCharacter; + COMP_KMXPLUS_DISP_ENTRY entries[]; + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_DISP) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_DISP) == LDML_LENGTH_DISP, "mismatched size of section disp"); + + + +/* ------------------------------------------------------------------ + * layr section + ------------------------------------------------------------------ */ + +struct COMP_KMXPLUS_LAYR_LIST { + KMX_DWORD hardware; + KMX_DWORD layer; + KMX_DWORD count; + KMX_DWORD minDeviceWidth; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_LAYR_LIST) == LDML_LENGTH_LAYR_LIST, "mismatched size of COMP_KMXPLUS_LAYR_LIST"); + +struct COMP_KMXPLUS_LAYR_ENTRY { + KMXPLUS_STR id; + KMX_DWORD mod; + KMX_DWORD row; + KMX_DWORD count; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_LAYR_ENTRY) == LDML_LENGTH_LAYR_ENTRY, "mismatched size of COMP_KMXPLUS_LAYR_ENTRY"); + +struct COMP_KMXPLUS_LAYR_ROW { + KMX_DWORD key; + KMX_DWORD count; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_LAYR_ROW) == LDML_LENGTH_LAYR_ROW, "mismatched size of COMP_KMXPLUS_LAYR_ROW"); + + +struct COMP_KMXPLUS_LAYR_KEY { + KMX_DWORD key; // index into key2 section +}; + +static_assert(sizeof(struct COMP_KMXPLUS_LAYR_KEY) == LDML_LENGTH_LAYR_KEY, "mismatched size of COMP_KMXPLUS_LAYR_KEY"); + +struct COMP_KMXPLUS_LAYR { + static const KMX_DWORD IDENT = LDML_SECTIONID_LAYR; + COMP_KMXPLUS_HEADER header; + KMX_DWORD listCount; + KMX_DWORD layerCount; + KMX_DWORD rowCount; + KMX_DWORD keyCount; + // entries, rows, and keys have a dynamic offset + // use COMP_KMXPLUS_LAYR_Helper to access. + // + // COMP_KMXPLUS_LAYR_LIST lists[]; + // COMP_KMXPLUS_LAYR_ENTRY entries[]; + // COMP_KMXPLUS_LAYR_ROW rows[]; + // COMP_KMXPLUS_LAYR_KEY keys[]; + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +/** + * Helper accessor for the dynamic part of a layr section. + */ +class COMP_KMXPLUS_LAYR_Helper { +public: + COMP_KMXPLUS_LAYR_Helper(); + /** + * Initialize the helper to point at a layr section. + * @return true if valid + */ + bool setLayr(const COMP_KMXPLUS_LAYR *newLayr); + bool valid() const; + + const COMP_KMXPLUS_LAYR_LIST *getList(KMX_DWORD list) const; + const COMP_KMXPLUS_LAYR_ENTRY *getEntry(KMX_DWORD entry) const; + const COMP_KMXPLUS_LAYR_ROW *getRow(KMX_DWORD row) const; + const COMP_KMXPLUS_LAYR_KEY *getKey(KMX_DWORD key) const; + +private: + const COMP_KMXPLUS_LAYR *layr; + bool is_valid; + const COMP_KMXPLUS_LAYR_LIST *lists; + const COMP_KMXPLUS_LAYR_ENTRY *entries; + const COMP_KMXPLUS_LAYR_ROW *rows; + const COMP_KMXPLUS_LAYR_KEY *keys; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_LAYR) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_LAYR) == LDML_LENGTH_LAYR, "mismatched size of section layr"); + +/* ------------------------------------------------------------------ + * key2 section + ------------------------------------------------------------------ */ +struct COMP_KMXPLUS_KEYS { + static const KMX_DWORD IDENT = LDML_SECTIONID_KEYS; + COMP_KMXPLUS_HEADER header; + KMX_DWORD keyCount; + KMX_DWORD flicksCount; + KMX_DWORD flickCount; + KMX_DWORD kmapCount; + // see helper for: keys sub-table + // see helper for: flick lists sub-table + // see helper for: flick elements sub-table + // see helper for: kmap sub-table + + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +struct COMP_KMXPLUS_KEYS_FLICK_ELEMENT { + KMXPLUS_LIST directions; + KMX_DWORD flags; + KMXPLUS_STR to; // string or codepoint +}; + +struct COMP_KMXPLUS_KEYS_FLICK_LIST { + KMX_DWORD count; + KMX_DWORD flick; // flick index + KMXPLUS_STR id; +}; + +struct COMP_KMXPLUS_KEYS_KEY { + KMXPLUS_STR to; + KMX_DWORD flags; + KMXPLUS_STR id; + KMXPLUS_STR switchId; // switch + KMX_DWORD width; // unit: 0.1 keys + KMXPLUS_LIST longPress; + KMXPLUS_STR longPressDefault; + KMXPLUS_LIST multiTap; + KMX_DWORD flicks; // index + + std::u16string get_string() const; +}; + +struct COMP_KMXPLUS_KEYS_KMAP { + KMX_DWORD vkey; + KMX_DWORD mod; + KMX_DWORD key; // index into key subtable +}; + +class COMP_KMXPLUS_KEYS_Helper { +public: + COMP_KMXPLUS_KEYS_Helper(); + /** + * Initialize the helper to point at a layr section. + * @return true if valid + */ + bool setKeys(const COMP_KMXPLUS_KEYS *newKeys); + inline bool valid() const { return is_valid; } + + const COMP_KMXPLUS_KEYS_KEY *getKeys(KMX_DWORD key) const; + const COMP_KMXPLUS_KEYS_FLICK_LIST *getFlickLists(KMX_DWORD list) const; + const COMP_KMXPLUS_KEYS_FLICK_ELEMENT *getFlickElements(KMX_DWORD element) const; + const COMP_KMXPLUS_KEYS_KMAP *getKmap(KMX_DWORD element) const; + + /** + * Search for a key by string id. + * @param strID id to search for + * @param index on exit, index of item if found. Undefined otherwise. + * @return pointer to key or nullptr + */ + const COMP_KMXPLUS_KEYS_KEY *findKeyByStringId(KMX_DWORD strId, KMX_DWORD &index) const; + +private: + const COMP_KMXPLUS_KEYS *key2; + bool is_valid; + const COMP_KMXPLUS_KEYS_KEY *keys; + const COMP_KMXPLUS_KEYS_FLICK_LIST *flickLists; + const COMP_KMXPLUS_KEYS_FLICK_ELEMENT *flickElements; + const COMP_KMXPLUS_KEYS_KMAP *kmap; +}; + +static_assert(sizeof(struct COMP_KMXPLUS_KEYS_KEY) == LDML_LENGTH_KEYS_KEY, "mismatched size of key2.key"); +static_assert(sizeof(struct COMP_KMXPLUS_KEYS_FLICK_ELEMENT) == LDML_LENGTH_KEYS_FLICK_ELEMENT, "mismatched size of key2.flick"); +static_assert(sizeof(struct COMP_KMXPLUS_KEYS_FLICK_LIST) == LDML_LENGTH_KEYS_FLICK_LIST, "mismatched size of key2.flicks"); +static_assert(sizeof(struct COMP_KMXPLUS_KEYS_KMAP) == LDML_LENGTH_KEYS_KMAP, "mismatched size of key2.kmap"); +static_assert(sizeof(struct COMP_KMXPLUS_KEYS) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_KEYS) == LDML_LENGTH_KEYS, "mismatched size of section key2"); + +/* ------------------------------------------------------------------ + * list section + ------------------------------------------------------------------ */ +struct COMP_KMXPLUS_LIST { + static const KMX_DWORD IDENT = LDML_SECTIONID_LIST; + COMP_KMXPLUS_HEADER header; + KMX_DWORD listCount; + KMX_DWORD indexCount; + // see helper for: lists sub-table + // see helper for: indices sub-table + + /** + * @brief True if section is valid. + */ + bool valid(KMX_DWORD length) const; +}; + +/** + * list.list subtable + */ +struct COMP_KMXPLUS_LIST_ITEM { + KMX_DWORD index; + KMX_DWORD count; +}; + +/** + * list.index + */ +typedef KMX_DWORD COMP_KMXPLUS_LIST_INDEX; + + +class COMP_KMXPLUS_LIST_Helper { +public: + COMP_KMXPLUS_LIST_Helper(); + /** + * Initialize the helper to point at a layr section. + * @return true if valid + */ + bool setList(const COMP_KMXPLUS_LIST *newList); + inline bool valid() const { return is_valid; } + + const COMP_KMXPLUS_LIST_ITEM *getList(KMX_DWORD list) const; + const COMP_KMXPLUS_LIST_INDEX *getIndex(KMX_DWORD index) const; + +private: + const COMP_KMXPLUS_LIST *list; + bool is_valid; + const COMP_KMXPLUS_LIST_ITEM *lists; + const COMP_KMXPLUS_LIST_INDEX *indices; +}; + + +static_assert(sizeof(struct COMP_KMXPLUS_LIST) % 0x4 == 0, "Structs prior to variable part should align to 32-bit boundary"); +static_assert(sizeof(struct COMP_KMXPLUS_LIST) == LDML_LENGTH_LIST, "mismatched size of section list"); +static_assert(sizeof(struct COMP_KMXPLUS_LIST_ITEM) == LDML_LENGTH_LIST_ITEM, "mismatched size of section list.lists subtable"); +static_assert(sizeof(COMP_KMXPLUS_LIST_INDEX) == LDML_LENGTH_LIST_INDEX, "mismatched size of section list.indices subtable"); + +/** + * @brief helper accessor object for KMX Plus data + * + */ +class kmx_plus { + public: + /** + * @brief Construct a new kmx_plus object. + * Caller must preserve the keyboard file memory + * until after this object is destroyed. + * + * @param keyboard the KMX data + * @param length length of the entire KMX file + */ + kmx_plus(const COMP_KEYBOARD *keyboard, size_t length); + // keep the next elements sorted + const COMP_KMXPLUS_DISP *disp; + const COMP_KMXPLUS_ELEM *elem; + const COMP_KMXPLUS_KEYS *key2; + const COMP_KMXPLUS_LAYR *layr; + const COMP_KMXPLUS_LIST *list; + const COMP_KMXPLUS_LOCA *loca; + const COMP_KMXPLUS_META *meta; + const COMP_KMXPLUS_SECT *sect; + const COMP_KMXPLUS_STRS *strs; + const COMP_KMXPLUS_TRAN *tran; + const COMP_KMXPLUS_VKEY *vkey; + inline bool is_valid() { return valid; } + COMP_KMXPLUS_LAYR_Helper layrHelper; + COMP_KMXPLUS_LIST_Helper listHelper; + COMP_KMXPLUS_KEYS_Helper key2Helper; + private: + bool valid; // true if valid +}; + +/** + * See above - undo workaround for variable arrays + */ +#if defined(_WIN32) +#pragma warning ( default : 4200 ) +#endif + +} // namespace kmx +} // namespace kbp +} // namespace km diff --git a/core/src/kmx/kmx_processevent.h b/core/src/kmx/kmx_processevent.h index ef799bb9fd1..a7fd9235395 100644 --- a/core/src/kmx/kmx_processevent.h +++ b/core/src/kmx/kmx_processevent.h @@ -12,6 +12,7 @@ #include #include #include +#include "debuglog.h" #include "kmx_base.h" #include "kmx_file.h" #include "kmx_context.h" @@ -29,7 +30,6 @@ namespace km { namespace kbp { namespace kmx { - /* Utility */ #define GLOBAL_ContextStackSize 80 @@ -57,7 +57,6 @@ class KMX_ProcessEvent { KMX_BOOL LoadKeyboard(km_kbp_path_name fileName, LPKEYBOARD *lpKeyboard); KMX_BOOL VerifyKeyboard(PKMX_BYTE filebase, size_t sz); KMX_BOOL VerifyChecksum(PKMX_BYTE buf, size_t sz); - PKMX_WCHAR StringOffset(PKMX_BYTE base, KMX_DWORD offset); #ifdef KMX_64BIT LPKEYBOARD CopyKeyboard(PKMX_BYTE bufp, PKMX_BYTE base); #else @@ -108,6 +107,10 @@ class KMX_ProcessEvent { KMX_Environment *GetEnvironment(); KMX_Environment const *GetEnvironment() const; INTKEYBOARDINFO const *GetKeyboard() const; + + // Utility function +public: + static PKMX_WCHAR StringOffset(PKMX_BYTE base, KMX_DWORD offset); }; inline KMX_BOOL KMX_ProcessEvent::IsCapsLockOn(KMX_DWORD modifiers) { @@ -121,41 +124,7 @@ struct char_to_vkey { bool shifted, caps; }; -struct modifier_names { - const char *name; - uint16_t modifier; -}; - extern const struct char_to_vkey s_char_to_vkey[]; -extern const char *s_key_names[]; -extern const struct modifier_names s_modifier_names[]; - -/* Debugging */ - -extern KMX_BOOL g_debug_ToConsole, g_debug_KeymanLog, g_silent; - -#ifdef _MSC_VER -#define DebugLog(msg,...) (km::kbp::kmx::ShouldDebug() ? km::kbp::kmx::DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg),__VA_ARGS__) : 0) -#define console_error(msg,...) write_console(TRUE, (msg), __VA_ARGS__) -#define console_log(msg,...) write_console(FALSE, (msg), __VA_ARGS__) -#else -#define DebugLog(msg,...) (ShouldDebug() ? DebugLog_1(__FILE__, __LINE__, __FUNCTION__, (msg), ##__VA_ARGS__) : 0) -#define console_error(msg,...) write_console(TRUE, (msg), ##__VA_ARGS__) -#define console_log(msg,...) write_console(FALSE, (msg), ##__VA_ARGS__) -#endif - -int DebugLog_1(const char *file, int line, const char *function, const char *fmt, ...); -const char *Debug_VirtualKey(KMX_WORD vk); -const char *Debug_UnicodeString(PKMX_WCHAR s, int x = 0); -const char *Debug_UnicodeString(std::u16string s, int x = 0); -const char *Debug_ModifierName(KMX_UINT modifiers); - -inline KMX_BOOL ShouldDebug() { - return g_debug_KeymanLog; -} - - -void write_console(KMX_BOOL error, const wchar_t *fmt, ...); } // namespace kmx } // namespace kbp diff --git a/core/src/kmx/kmx_processor.cpp b/core/src/kmx/kmx_processor.cpp index 30cc942671e..81da199c9cc 100644 --- a/core/src/kmx/kmx_processor.cpp +++ b/core/src/kmx/kmx_processor.cpp @@ -249,7 +249,8 @@ kmx_processor::process_event( km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state, - uint8_t is_key_down + uint8_t is_key_down, + uint16_t /* event_flags */ ) { // Construct a context buffer from the items std::u16string ctxt; @@ -257,11 +258,10 @@ kmx_processor::process_event( for (auto c = cp.begin(); c != cp.end(); c++) { switch (c->type) { case KM_KBP_CT_CHAR: - if (Uni_IsSMP(c->character)) { - ctxt += Uni_UTF32ToSurrogate1(c->character); - ctxt += Uni_UTF32ToSurrogate2(c->character); - } else { - ctxt += (km_kbp_cp)c->character; + { + km::kbp::kmx::char16_single buf; + const int len = km::kbp::kmx::Utf32CharToUtf16(c->character, buf); + ctxt.append(buf.ch, len); } break; case KM_KBP_CT_MARKER: diff --git a/core/src/kmx/kmx_processor.hpp b/core/src/kmx/kmx_processor.hpp index 0107d45f859..124feb3a3b8 100644 --- a/core/src/kmx/kmx_processor.hpp +++ b/core/src/kmx/kmx_processor.hpp @@ -36,7 +36,8 @@ namespace kbp km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state, - uint8_t is_key_down + uint8_t is_key_down, + uint16_t event_flags ) override; km_kbp_attr const & attributes() const override; diff --git a/core/src/kmx/kmx_xstring.h b/core/src/kmx/kmx_xstring.h index cc85c934f2d..fafa277ed79 100644 --- a/core/src/kmx/kmx_xstring.h +++ b/core/src/kmx/kmx_xstring.h @@ -6,15 +6,57 @@ namespace km { namespace kbp { namespace kmx { +/** + * @brief True if a lead surrogate + * \def Uni_IsSurrogate1 + */ #define Uni_IsSurrogate1(ch) ((ch) >= 0xD800 && (ch) <= 0xDBFF) +/** + * @brief True if a trail surrogate + * \def Uni_IsSurrogate2 + */ #define Uni_IsSurrogate2(ch) ((ch) >= 0xDC00 && (ch) <= 0xDFFF) -#define Uni_IsSMP(ch) ((ch) >= 0x10000) +/** + * @brief Returns true if BMP (Plane 0) + * \def Uni_IsBMP + */ +#define Uni_IsBMP(ch) ((ch) < 0x10000) + +/** + * @brief Convert two UTF-16 surrogates into one UTF-32 codepoint + * @param ch lead surrogate - Uni_IsSurrogate1(ch) must == true + * @param cl trail surrogate - Uni_IsSurrogate2(cl) must == true + * \def Uni_SurrogateToUTF + */ #define Uni_SurrogateToUTF32(ch, cl) (((ch) - 0xD800) * 0x400 + ((cl) - 0xDC00) + 0x10000) +/** + * @brief Convert UTF-32 BMP to UTF-16 BMP + * @param ch codepoint - Uni_IsBMP(ch) must == true + * \def Uni_UTF32BMPToUTF16 + */ +#define Uni_UTF32BMPToUTF16(ch) (ch & 0xFFFF) + #define Uni_UTF32ToSurrogate1(ch) (char16_t)(((ch) - 0x10000) / 0x400 + 0xD800) #define Uni_UTF32ToSurrogate2(ch) (char16_t)(((ch) - 0x10000) % 0x400 + 0xDC00) +/** + * char16_t array big enough to hold a single Unicode codepoint, + * including trailing null. + */ +typedef struct { + char16_t ch[3]; +} char16_single; + +/** + * Convert a UTF-32 codepoint to UTF-16 code unit(s). + * @param ch32 input codepoint + * @param ch16 output buffer + * @return int length returned (not including null). Will return 1 (BMP) or 2 + */ +int Utf32CharToUtf16(const KMX_DWORD ch32, char16_single& ch16); + PKMX_WCHAR incxstr(PKMX_WCHAR p); PKMX_WCHAR decxstr(PKMX_WCHAR p, PKMX_WCHAR pStart); int xstrlen(PKMX_WCHAR p); @@ -38,6 +80,59 @@ km_kbp_cp *u16dup(km_kbp_cp *src); //KMX_BOOL MapUSCharToVK(KMX_WORD ch, PKMX_WORD puKey, PKMX_DWORD puShiftFlags); +// --- implementation --- + +inline int +Utf32CharToUtf16(const KMX_DWORD ch32, char16_single &ch16) { + int len; + if (Uni_IsBMP(ch32)) { + len = 1; + ch16.ch[0] = Uni_UTF32BMPToUTF16(ch32); + ch16.ch[1] = 0; + } else { + len = 2; + ch16.ch[0] = Uni_UTF32ToSurrogate1(ch32); + ch16.ch[1] = Uni_UTF32ToSurrogate2(ch32); + ch16.ch[2] = 0; + } + return len; +} + +/** + * Convert a u16 string to a u32 string. + * Mismatched surrogates or sliced surrogates are replaced with U+FFFD (replacement character). + * @param source UTF-16 string + * @return a UTF-32 string + */ +inline std::u32string +u16string_to_u32string(const std::u16string &source) { + std::u32string out; + + for (auto ptr = source.begin(); ptr < source.end(); ptr++) { + const char16_t lead = *ptr; + if (Uni_IsSurrogate1(lead)) { + ptr++; + if (ptr == source.end()) { + // DebugLog("End of string during surrogate pair"); + out.push_back(0xFFFD); // error + return out; + } + const char16_t trail = *ptr; + if (!Uni_IsSurrogate2(trail)) { + out.push_back(0xFFFD); // error, mismatched lead surrogate + ptr--; // reprocess remaining char + } else { + out.push_back(Uni_SurrogateToUTF32(lead, trail)); + } + } else if (Uni_IsSurrogate2(lead)) { + out.push_back(0xFFFD); // error - mismatched trail surrogate + } else { + out.push_back(lead); + } + } + return out; +} + } // namespace kmx } // namespace kbp } // namespace km diff --git a/core/src/ldml/C7043_ldml.md b/core/src/ldml/C7043_ldml.md new file mode 100644 index 00000000000..b5fbf5bdca2 --- /dev/null +++ b/core/src/ldml/C7043_ldml.md @@ -0,0 +1,536 @@ +# KMX Plus Binary Format #7043 + +- copied from + +- Authors: MD SL + +## C7043.0 Introduction + +This document discusses the binary format for KMXPlus, which contains keyboards +converted from source LDML data. + +Draft spec PR: + +## C7043.1 Principles + +- The data described here is located at byte offset `dpKMXPlus`. +- All integer values are unsigned 32-bit little-endian unless otherwise + specified. +- All strings are UTF-16LE unless otherwise specified. (See the `strs` section.) + String data items are identified with `str:`, indicating a 32 bit index into + the `strs` table. +- All offsets are 32-bit little-endian values. For all sections except for the + `'sect'` section (which see), offsets are relative to the beginning of each + section. + +## C7043.2 Sections + +- Data is divided into several sections. The very first section is the `'sect'` + section which is the table of contents. +- All sections begin with a 32-bit (four character) section identifier, and a + 32-bit section size. +- Other than the `sect` table itself, the rest of the sections follow in binary + order in the file. In other words, the binary ordering of the section + identifiers determines the order of the file layout. +- All sections other than the `sect` table are optional + +### C7043.2.1 `sect`—Section Table of contents + +The very first section is a table of contents listing the rest of the sections. +The table of contents does not list itself. + +This is the only section where all byte offsets are relative to the value of +`dpKMXPlus`. + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +| 0 | 32 | ident | `sect` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | total | int: KMXPlus entire length | +|12 | 32 | count | int: Number of following section headers | + +Then for `count` repetitions: + +| ∆ | Bits | Name | Description | +|---|------|---------|---------------------------------------------------| +|16+| 32 | sect | Section identity, e.g. `loca` | +|20+| 32 | offset | off: offset relative to `dpKMXPlus` of section | + +This list is in sorted order based on the `sect` identifier. + +### C7043.2.2 `bksp`—Backspace transform + +See [C7043.2.11](#c7043211-tran-finl-and-bksptransforms). + +### C7043.2.3 `elem`—Transform and Reorder element strings + +| ∆ | Bits | Name | Description | +|---|------|----------|------------------------------------------| +| 0 | 32 | ident | `elem` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | count | int: Number of element string entries | + +Then for each element string: + +| ∆ | Bits | Name | Description | +|---|------|---------------|-----------------------------------------------| +|16+| 32 | offset | off: Offset to element string | +|20+| 32 | length | int: Number of elements in element string | + +The first entry in the element string list MUST be the null element string, +which has zero length and zero offset. + +Each element string is made up of elements with the following item structure: + +| ∆ | Bits | Name | Description | +|---|------|-----------|----------------------------------------------------------| +| 0 | 32 | element | str: output string OR UTF-32LE codepoint | +| 4 | 32 | flags | flags and order values | + +- `element`: either a UnicodeSet stored in a `strs` section entry, or a UTF-32LE + codepoint; see also `unicode_set` flag. +- `flags`: a 32-bit bitfield defined as below: + + | Bit position | Meaning | Description | + |--------------|---------------|----------------------------------| + | 0 | unicode_set | `element` is 0: UTF-32LE, 1: str | + | 1 | tertiary_base | 1: tertiary_base is true | + | 2 | prebase | 1: prebase is true | + | 3-15 | reserved | reserved | + | 16-23 | order | signed int: -128 to +127 | + | 24-31 | tertiary | signed int: -128 to +127 | + + For transforms, only `flags.unicode_set` will be used. The remaining flags are + used for reorders, `from` attribute only. + +### C7043.2.4 `finl`—Final transform + +See [C7043.2.11](#c7043211-tran-finl-and-bksptransforms). + +### C7043.2.5 Removed: `keys` + +_this section has been merged into the new `keys.kmap`_ + +### C7043.2.6 `loca`—Locales + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +| 0 | 32 | ident | `loca` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | count | int: Number of locales | + +`count` is always ≥1, because a keyboard always has a primary locale identifier. + +For each locale ID in `count` + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +|16+| 32 | locale | str: Locale ID in BCP47 format | + +The first locale ID is always the primary locale identifier. The rest of the +locale IDs (starting at offset 16) are in sorted binary order. + +### C7043.2.7 `meta`—Metadata + +| ∆ | Bits | Name | Description | +|---|------|---------------|-------------------------------------| +| 0 | 32 | ident | `meta` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | author | str: Keyboard author | +|12 | 32 | conform | str: CLDR 'conformsTo' version | +|16 | 32 | layout | str: layout type | +|20 | 32 | normalization | str: normalization mode | +|24 | 32 | indicator | str: indicator | +|28 | 32 | settings | int: keyboard settings | + +The `settings` is a 32-bit bitfield as below: + +| Bit position | Meaning | Description | +|--------------|------------------|------------------------------| +| 0 | fallback | fallback=omit | +| 1 | transformFailure | transformFailure=omit | +| 2 | transformPartial | transformPartial=hide | + +### C7043.2.8 `name`—Names + +Defines the names of the keyboard as found in the source `` element. +While this section is optional in the binary format, in practice it will always +be present, as the source format requires at least one name. + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +| 0 | 32 | ident | `name` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | count | int: Number of names | + +Note that `count` is always ≥1, as the source format requires at least one name. + +For each name in `count`: + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +|16+| 32 | name | str: A name for the keyboard | + +Names are stored in source file order, and the semantic meaning of each name is +not defined here. + +### C7043.2.9 `ordr`—Reorders + +| ∆ | Bits | Name | Description | +|---|------|----------|------------------------------------------| +| 0 | 32 | ident | `ordr` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | count | int: Number of reorders | + +For each reorder item: + +| ∆ | Bits | Name | Description | +|---|------|----------|----------------------------------------------------------| +|16+| 32 | elements | elem: string of elements, index into `elem` section | +|20+| 32 | before | elem: look-behind required match, index into `elem` | + +- `elements`: index into the `elem` section, coding `from`, `order`, `tertiary`, + `tertiary_base`, and `prebase` properties. +- `before`: follows `transform/@before` + +### C7043.2.10 `strs`—Strings + +All strings are stored in the Strings section. + +| ∆ | Bits | Name | Description | +|---|------|---------------|-------------------------------------| +| 0 | 32 | ident | `strs` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | count | int: Number of strings | + +Then for each string: + +| ∆ | Bits | Name | Description | +|---|------|---------------|-----------------------------------------------| +|16+| 32 | offset | off: Offset to string | +|20+| 32 | length | int: Length of string in UTF-16LE code units | + +After the string offset table comes the actual UTF-16LE data. There is a null +(\u0000) after each string, which is _not_ included in the string length. + +The string offset table, and then strings themselves, are sorted according to a +binary codepoint sort, not including the null. + +The first string in the string table MUST always be the zero-length string. A +zero-length string is considered the same as a null string. For metadata fields, +references to this zero-length string will have the dword index value 0, which +can safely be interpreted as "not set". There may be other locations which have +required strings, but for which a zero-length string is permissible, and this +index value of 0 can be used for that purpose. + +A distinction between zero-length string and optional should be avoided (e.g. +the difference between "" and null in Javascript). If this is truly required, a +separate flag field must be used to denote the difference. + +### C7043.2.11 `tran`, `finl`, and `bksp`—Transforms + +All three of these tables have the same format. and differ only in their +identity. The simple transform table has the ident `tran`; the final transform +table has the ident `finl`, and the backspaces table has the ident `bksp`. + + +| ∆ | Bits | Name | Description | +|---|------|----------|------------------------------------------| +| 0 | 32 | ident | `tran` / `finl` / `bksp` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | count | int: Number of transforms | + + +The transforms are sorted in binary order based on the `from` field. + +For each transform: + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +|16+| 32 | from | elem: combination of characters | +|20+| 32 | to | str: output text | +|24+| 32 | before | elem: look-behind text (0 = omitted) | +|28+| 32 | flags | int: per-transform flags | + +- `from`: the source text, index into `elem` section. +- `to`: sequence of Unicode codepoints that replace `from`. May be the null + string for `bksp` entries. +- `before`: optional look-behind text occurring before `from`, index into `elem` + section +- `flags`: a 32-bit bitfield defined as below: + + | Bit position | Meaning | Description | + |--------------|----------|----------------------| + | 0 | error | 1: `error="fail"` | + +### C7043.2.12 `vkey`—VKey Map + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +| 0 | 32 | ident | `vkey` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | count | int: Number of vkeys | + +The keys are sorted in binary order based on the `vkey` field. + +For each key: + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +|16+| 32 | vkey | int: source vkey ID (0…255) | +|20+| 32 | target | int: target vkey ID (0…255) | + +- `vkey`: Is the standard vkey, 0-255 +- `target`: Is the target (resolved) vkey, 0-255. + +### C7043.2.13 `layr`—Layers list + +Represents layers on the keyboard. +- Each key entry corresponds to a key on the row + +| ∆ | Bits | Name | Description | +|---|------|------------|------------------------------------------| +| 0 | 32 | ident | `layr` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | listCount | int: Number of layer lists | +|12 | 32 | layerCount | int: number of layer entries | +|16 | 32 | rowCount | int: number of row entries | +|20 | 32 | keyCount | int: number of key entries | +|24 | var | lists | layer list sub-table | +| - | var | layers | layers sub-table | +| - | var | rows | rows sub-table | +| - | var | keys | keys sub-table | + +### `layr.lists` subtable + +Each layer list corresponds to one `` element. +There are `listCount` total lists. + +| ∆ | Bits | Name | Description | +|---|------|------------------|--------------------------------------------| +| 0+| 32 | hardware | int: enumeration for hardware layout | +| 4+| 32 | layer | int: index to first layer element | +| 8+| 32 | count | int: number of layer elements in this list | +|12+| 32 | minDeviceWidth | int: min device width in millimeters, or 0 | + +- `hardware`: an enumeration with the following values: + - 0: `touch` (non-hardware) + - 1: `abnt2` + - 2: `iso` + - 3: `jis` + - 4: `us` + +See UTS #35 section 7 for details about these values. + +Layer lists are sorted by `hardware` enum, then minDeviceWidth ascending. + +### `layr.layers` subtable + +Each layer entry corresponds to one `` element +There are `layerCount` total layer entries. + +| ∆ | Bits | Name | Description | +|---|------|------------|------------------------------------------------| +| 0+| 32 | id | str: layer id such as `base` or `shift` | +| 4+| 32 | mod | int: modifier key flags | +| 8+| 32 | row | int: index into rows area (next section) | +|12+| 32 | count | int: number of `rows` elements for this layer | + +- for `mod` see `keys.key.mod` + +### `layr.rows` subtable + +Each row entry corresponds to one `` element +There are `rowCount` total row entries. + +| ∆ | Bits | Name | Description | +|---|------|------------|----------------------------------------| +| 0+| 32 | key | int: index into key element | +| 4+| 32 | count | int: count of key elements in this row | + +### `layr.keys` subtable + +Each key entry corresponds to a key in the row. +There are `keyCount` total key entries. + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +| 0+| 32 | key | str: key id | + +### C7043.2.14 `disp`—Display list + +| ∆ | Bits | Name | Description | +|---|------|---------------|------------------------------------------| +| 0 | 32 | ident | `disp` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | count | int: Total number of disp elements | +|12 | 32 | baseCharacter | str: If non-null, default base. | + +The default `baseCharacter` is U+25CC, if `baseCharacter` is null. + +For each element: + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +|32+| 32 | to | str: to string | +|36+| 32 | display | str: output display string | + +Entries are sorted in a binary codepoint sort on the `to` field. + +### C7043.2.15 `key2`—Extended keybag + +| ∆ | Bits | Name | Description | +|---|------|-------------|------------------------------------------| +| 0 | 32 | ident | `key2` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | keyCount | int: Number of keys | +|12 | 32 | flicksCount | int: Number of flick lists | +|16 | 32 | flickCount | int: Number of flick elements | +|20 | 32 | kmapCount | int: Number of kmap elements | +|24 | var | keys | keys sub-table | +| - | var | flicks | flick lists sub-table | +| - | var | flick | flick elements sub-table | +| - | var | kmap | key map sub-table | + +#### `key2.keys` subtable + +For each key: + +| ∆ | Bits | Name | Description | +|---|------|---------------- |----------------------------------------------------------| +| 0+| 32 | to | str: output string OR UTF-32LE codepoint | +| 4+| 32 | flags | int: per-key flags | +| 8+| 32 | id | str: key id | +|12+| 32 | switch | str: layer id to switch to | +|16+| 32 | width | int: key width*10 (supports 0.1 as min width) | +|20+| 32 | longPress | list: index into `list` section with longPress list or 0 | +|24+| 32 | longPressDefault | str: default longpress target or 0 | +|28+| 32 | multiTap | list: index into `list` section with multiTap list or 0 | +|32+| 32 | flicks | int: index into `key2.flicks` subtable | + +- `id`: The original string id from XML. This may be 0 to save space (i.e. omit the string id). +- `flags`: Flags is a 32-bit bitfield defined as below: + +| Bit position | Meaning | Description | +|--------------|-----------|---------------------------------------------| +| 0 | extend | 0: `to` is a char, 1: `to` is a string | +| 1 | gap | 1 if the key is a gap | +| 2 | transform | 1 if the key is transform=no | + +- `to`: If `extend` is 0, `to` is a UTF-32LE codepoint. If `extend` is 1, `to` + is a 32 bit index into the `strs` table. The string may be zero-length. + +#### `key2.flicks` flick list subtable + +For each flicks in the flick list: + +| ∆ | Bits | Name | Description | +|---|------|---------------- |----------------------------------------------------------| +| 0+| 32 | count | int: number of flick elements in this flick | +|12+| 32 | flick | int: index into `flick` subtable for first flick element | +|16+| 32 | id | str: flick id | + +- `id`: The original string id from XML. This may be 0 to save space (i.e. omit the string id). + +Elements are ordered by the string id. + +If this section is present, it must have a 'flicks' in the list at position zero with count=0, index=0 and id=0 meaning 'no flicks'. +#### `key2.flick` flick element subtable + +For each flick element: + +| ∆ | Bits | Name | Description | +|---|------|---------------- |----------------------------------------------------------| +| 0+| 32 | directions | list: index into `list` section with direction list | +| 8+| 32 | flags | int: per-key flags | +|12+| 32 | to | str: output string, or ucs32: output char, see flags | + +If this section is present, it must have a 'flick element' at position zero with directions=0, flags=0, and to=0 meaning 'no flick'. + +There is not a 'null' flick element at the end of each list. + +Elements are ordered by the `flicks.id`, and secondarily by the directions list id. + +- `flags`: Flags is a 32-bit bitfield defined as below: + +| Bit position | Meaning | Description | +|--------------|-----------|---------------------------------------------| +| 0 | extend | 0: `to` is a char, 1: `to` is a string | + +#### `key2.kmap` key map subtable + +This table (formerly the `keys` section) +The keys are sorted in ascending order based on the `vkey`, `mod` fields. + +For each key: + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +|16+| 32 | vkey | int: vkey ID | +|20+| 32 | mod | int: modifier key flags | +|24+| 32 | key | int: index into `key` sibling subtable | + +- `vkey`: If this is 0-255, it is the resolved standard/predefined vkey (K_A, + etc.). It is resolved because the `vkeyMap` from LDML has already been + applied. If this is 256 or above, it is a custom touch layout vkey generated + by the compiler. +- `mod`: 32-bit bitfield defined as below. Little endian values. + +| Bit position | Meaning | Comment | +|--------------|----------|---------------------------------------------| +| `0x00000000` | `none` | All zeros = no modifiers | +| 0 | `ctrlL` | `ctrl = ctrlL + ctrlR` | +| 1 | `ctrlR` | | +| 2 | `altL` | | +| 3 | `altR` | `alt` (both) = `altL + altR` | +| 4 | `shift` | Either shift | +| 8 | `caps` | | + +TODO-LDML: Note that conforming to other keyman values, left versus right shift +cannot be distinguished. Also note that `cmd` and `opt` do not match +other keyman values. + +TODO-LDML: Note that 'Current' LDML spec allows `shiftL`/`shiftR`, `opt`, +and `cmd` but there is a request to drop these. These four are not represented +here. + + +### C7043.2.16 `list`—String lists + +| ∆ | Bits | Name | Description | +|---|------|---------------|------------------------------------------| +| 0 | 32 | ident | `list` | +| 4 | 32 | size | int: Length of section | +| 8 | 32 | listCount | int: Total number of lists elements | +|12 | 32 | indexCount | int: Total number of index elements | +|16 | var | lists | list sub-table | +| - | var | indices | index sub-table | + +#### `list.lists` sub-table + +The lists entry at location 0 is defined to have index=0 and count=0, +representing a 0-length list. + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +| 0+| 32 | index | int: Index into 'indices' subtable | +| 4+| 32 | count | int: Number of indices in this list | + +This data should be sorted in binary lexical order of the substrings. + +#### `list.indices` sub-table + +The index entry at location 0 is defined to have str=0, +representing a 0-length string. + +| ∆ | Bits | Name | Description | +|---|------|---------|------------------------------------------| +| 0+| 32 | str | str: string index into `strs` table | + +These indices are a pool of indexes into the string table. +The strings order are significant. There is not a 'null' string at the end of each list. + +## TODO-LDML: various things that need to be completed here or fixed in-spec + +> * UnicodeSets +> * spec: ABNT2 key has hex value 0xC1 (even if kbdus.dll doesn't produce that) diff --git a/core/src/ldml/C7532_ldml_updating.md b/core/src/ldml/C7532_ldml_updating.md new file mode 100644 index 00000000000..d5c75188b08 --- /dev/null +++ b/core/src/ldml/C7532_ldml_updating.md @@ -0,0 +1,76 @@ +# How to update KMXPlus sections + +A diary. +By Steven R. Loomis + +Keyman Section Update Journal + +working on ‘layr’, using ‘disp’ as a model from https://github.com/keymanapp/keyman/pull/7568 + +## Constants and Scaffolding + +- *Edit/Commit*: `core/include/ldml/keyboardprocessor_ldml.ts` + - update `SectionIdent` and keep in order + - update `SectionMap` and keep in order + - add a comment block in order `layr section` + - add `length_layr` with the nonvariable length + - add a `length_layr_*` for each subitem + - add parameters for each flag/bitfield + - Check indentation, check for copypasta errs! +- Run: `./core/tools/ldml-const-builder/build.sh clean build run` +- Verify/Commit: `core/include/ldml/keyboardprocessor_ldml.h` + +## XML changes + +- `resources/standards-data/ldml-keyboards/techpreview/` : update / reimport / fix fixup script if needed +- E/C: `common/web/types/src/ldml-keyboard/ldml-keyboard-xml.ts` + - add to `LKKeyboard` and subproperties as needed to support the structure on the XML side +- Now would be a good time to stop and make sure everything compiles. It didn’t, there was an unrelated issue with snprintf! + +## In-memory data: Phase 1 + +- `common/web/types/src/kmx/kmx-plus.ts` + - update `KMXPlusData` to include new section + - add the new section and any in-memory data for the compiler + - It’s enough temporarily to add `export class Sect extends Section{ /* TODO-LDML */};` for now so that it compiles, and come back to it +- `core/src/kmx/kmx_plus.h` + - add new structs +- `core/src/kmx/kmx_plus.cpp` + - add validate implementation for the section and any new structs + - update `kmx_plus::kmx_plus()` to include the loader +- `common/web/types/src/kmx/kmx-plus.ts` + - Also update class KMXPlusFile to include the actual binary format (coordinate with `kmx_plus.h`) +- E/C `common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts` if there’s any changing to the boxing needed + +## In-memory data: Phase 2 + +- first the compiler tests + - add a section in `developer/src/kmc-keyboard/test/fixtures/sections` if needed + - add a test case such as `developer/src/kmc-keyboard/test/test-key2.ts` +- add a compiler + - `developer/src/kmc-keyboard/compiler/key2.ts` + - link it in to `developer/src/kmc-keyboard/src/compiler/compiler.ts` + - add the import + - add to `SECTION_COMPILERS` + + - Note: the in-memory compiler can affect `basic.kmx` even _before_ you add the section writing code. How? Simple… `Strs.allocString()` is called for the in-memory structures, so the string table will start growing even before those strings are actually used by the new sections. This is why it's fine to ignore the basic failure until you actually do the KMXPlus write. + +## Writing out + +- The moment you've been waiting for! Crack open `common/web/types/src/kmx/kmx-plus-builder/kmx-plus-builder.ts` and do it. + - Add `import { BUILDER_DISP, build_disp } from './build-disp.js';` to the top (and a new file to go with it) — and, in order + - Add `private sect_disp: BUILDER_DISP` to `class KMXPlusBuilder {` — and, in order + - add `this.sect_disp = build_disp(this.file.kmxplus, this.sect_strs);` to the `build()` function. Include any other sections that need to be cross referenced. + - Update `finalize_sect` and add `offset = this.finalize_sect_item(this.sect_disp, offset);` + - Finally, add `this.emitSection(file, this.file.COMP_PLUS_DISP, this.sect_disp);` to `compile()` — and, in order. + - Note that some variable length parts (such as the actual text data in `strs`) are sometimes in a separate emit function. Anything that's not in the `COMP_PLUS_STRS` `r.Struct` definition needs one of these. + - Also note that restructure will happily ignore (write zeros for) any fields where the BUILDER_* fields don't match the COMP_PLUS_* fields. + +- update basic.xml and basic.txt + - Tweak `eveloper/src/kmc-keyboard/test/fixtures/basic.xml` as needed + - You can use `developer/src/kmc-keyboard/build.sh build-fixtures` which will generate these. The two .kmx files are supposed to match: if not, fix `basic.txt` or fix other bugs. + - `developer/src/kmc-keyboard/build/test/fixtures/basic-txt.kmx` - KMX generated from basic.txt. + - `developer/src/kmc-keyboard/build/test/fixtures/basic-xml.kmx` - KMX generated from basic.xml. + - `developer/src/kmc-keyboard/build/test/fixtures/basic-xml.kvk` - KVK generated from basic.xml. + +## more to come diff --git a/core/src/ldml/ldml_processor.cpp b/core/src/ldml/ldml_processor.cpp new file mode 100644 index 00000000000..8f1f02d4b0b --- /dev/null +++ b/core/src/ldml/ldml_processor.cpp @@ -0,0 +1,349 @@ +/* + Copyright: © SIL International. + Description: This is an implementation of the LDML keyboard spec 3.0. + Create Date: 5 Aug 2022 + Authors: Marc Durdin (MD) +*/ + +#include +#include "ldml/ldml_processor.hpp" +#include "state.hpp" +#include "kmx_file.h" +#include "kmx/kmx_plus.h" +#include "kmx/kmx_xstring.h" +#include "ldml/keyboardprocessor_ldml.h" +#include "kmx/kmx_file_validator.hpp" +#include "debuglog.h" +#include + +namespace { + constexpr km_kbp_attr const engine_attrs = { + 256, + KM_KBP_LIB_CURRENT, + KM_KBP_LIB_AGE, + KM_KBP_LIB_REVISION, + KM_KBP_TECH_LDML, + "SIL International" + }; +} + +// using km::kbp::kmx::ShouldDebug; // for DebugLog + +namespace km { +namespace kbp { + + +ldml_processor::ldml_processor(path const & kb_path, const std::vector &data) +: abstract_processor( + keyboard_attributes(kb_path.stem(), KM_KBP_LMDL_PROCESSOR_VERSION, kb_path.parent(), {}) + ), _valid(false), transforms(), keys() +{ + + if(data.size() <= sizeof(kmx::COMP_KEYBOARD_EX)) { + DebugLog("data.size %zu too small", data.size()); + return; + } + +// // Locate the structs here, but still retain ptrs to the raw structs. + kmx::KMX_FileValidator* comp_keyboard = (kmx::KMX_FileValidator*)data.data(); + + // Perform the standard validation + if(!comp_keyboard->VerifyKeyboard(data.size())) { + DebugLog("VerifyKeyboard() returned false"); + return; + } + + kmx::kmx_plus kplus(comp_keyboard, data.size()); // slices to a COMP_KEYBOARD + + if(!kplus.is_valid()) { + DebugLog("kmx_plus.is_valid is false"); + return; + } + + // Now, if we have keys, use them. + if (kplus.key2 != nullptr) { + // read all keys into array + for (KMX_DWORD i=0; ikmapCount; i++) { + std::u16string str; + auto kmapEntry = kplus.key2Helper.getKmap(i); + assert(kmapEntry != nullptr); + // now look up the key + auto keyEntry = kplus.key2Helper.getKeys(kmapEntry->key); + assert(keyEntry != nullptr); + + if (keyEntry->flags && LDML_KEYS_KEY_FLAGS_EXTEND) { + if (nullptr == kplus.strs) { + DebugLog("for keys: kplus.strs == nullptr"); // need a string table to get strings + assert(false); + return; + } + str = kplus.strs->get(keyEntry->to); + } else { + str = keyEntry->get_string(); + } + keys.add((km_kbp_virtual_key)kmapEntry->vkey, (uint16_t)kmapEntry->mod, str); + } + } // else: no keys! but still valid. Just, no keys. + if (kplus.tran != nullptr) { + if (nullptr == kplus.elem) { + DebugLog("for tran: kplus.elem == nullptr"); + assert(false); + return; + } + if (nullptr == kplus.strs) { + DebugLog("for tran: kplus.strs == nullptr"); // need a string table to get strings + assert(false); + return; + } + for (KMX_DWORD i = 0; i < kplus.tran->count; i++) { + const kmx::COMP_KMXPLUS_TRAN_ENTRY &entry = kplus.tran->entries[i]; + // 'to' is always a string, unlike keys + const std::u16string tostr = kplus.strs->get(entry.to); + // now, fetch the 'from' + ldml::string_list list; + + // TODO-LDML: before= + // TODO-LDML: error= + + // process From + const KMX_DWORD elemNo = entry.from; + if (elemNo >= kplus.elem->count) { + DebugLog("tran[%d].from = %d, out of range for elem->count=%d", i, elemNo, kplus.elem->count); + assert(false); + return; + } + + // TODO-LDML: refactor this + KMX_DWORD elemListLength = 0; + const kmx::COMP_KMXPLUS_ELEM_ELEMENT *elemList = kplus.elem->getElementList(elemNo, elemListLength); + if (elemList == nullptr) { + DebugLog("tran[%d].from = %d, could not load element list", i, elemNo); + assert(false); + return; + } + if (elemListLength == 0) { + DebugLog("tran[%d].from = %d, element list has 0 length", i, elemNo); + assert(false); + return; + } + + // now we have an array of elements + for (KMX_DWORD e = 0; e < elemListLength; e++) { + const kmx::COMP_KMXPLUS_ELEM_ELEMENT &element = elemList[e]; + std::u16string str; + if (element.flags && LDML_ELEM_FLAGS_UNICODE_SET) { + str = kplus.strs->get(element.element); + // TODO-LDML: actually a UnicodeSet here + } else { + str = element.get_string(); // Get single UTF-16 + } + list.push_back(str); // add the string to the end + } + // Now, add the list to the map + transforms.add(list, tostr); + } + } + // Only valid if we reach here + DebugLog("_valid = true"); + _valid = true; +} + +bool ldml_processor::is_kmxplus_file(path const & kb_path, std::vector& data) { +// TODO-LDML: we should refactor all the core components to delegate file loading +// to the Engine, which requires an API change, but this makes delivery +// of keyboard files more flexible under more WASM. + + std::ifstream file(static_cast(kb_path), std::ios::binary | std::ios::ate); + if(!file.good()) { + return false; + } + const std::streamsize size = file.tellg(); + if(size >= KMX_MAX_ALLOWED_FILE_SIZE) { + return false; + } + + file.seekg(0, std::ios::beg); + + data.resize((size_t)size); + if(!file.read((char *) data.data(), size)) { + return false; + } + + file.close(); + + const kmx::PCOMP_KEYBOARD comp_keyboard = (kmx::PCOMP_KEYBOARD)data.data(); + + if(comp_keyboard->dwIdentifier != KMX_DWORD(FILEID_COMPILED)) { + return false; + } + + if(comp_keyboard->dwFileVersion < VERSION_160 || (comp_keyboard->dwFlags & KF_KMXPLUS) == 0) { + return false; + } + + // A KMXPlus file is in the buffer (although more validation is required and will + // be done in the constructor) + return true; +} + +km_kbp_status +ldml_processor::process_queued_actions( + km_kbp_state *state +) { + assert(state); + if (!state) + return KM_KBP_STATUS_INVALID_ARGUMENT; + // TODO Implement + return KM_KBP_STATUS_OK; +} + +bool ldml_processor::queue_action( + km_kbp_state * state, + km_kbp_action_item const* action_item +) +{ + assert(state); + assert(action_item); + if ((!state) || (!action_item)) + return false; + return false; +} + +km_kbp_status +ldml_processor::process_event( + km_kbp_state *state, + km_kbp_virtual_key vk, + uint16_t modifier_state, + uint8_t is_key_down, + uint16_t /*event_flags*/ // TODO-LDML: unused... for now... +) { + assert(state); + if (!state) + return KM_KBP_STATUS_INVALID_ARGUMENT; + + if (!is_key_down) { + // TODO: Implement caps lock handling + state->actions().clear(); + state->actions().commit(); + return KM_KBP_STATUS_OK; + } + + try { + // At the start of every process_event always clear the action_items + state->actions().clear(); + + switch (vk) { + case KM_KBP_VKEY_BKSP: + { + KMX_DWORD last_char = 0UL; + // attempt to get the last char + auto end = state->context().rbegin(); + if(end != state->context().rend()) { + if((*end).type == KM_KBP_CT_CHAR) { + last_char = (*end).character; + } + } + if (last_char == 0UL) { + state->actions().push_backspace(KM_KBP_BT_UNKNOWN); + } else { + state->actions().push_backspace(KM_KBP_BT_CHAR, last_char); + } + state->context().pop_back(); + } + break; + default: + // from kmx_processor.cpp + // Construct a context buffer from the items up until the last KM_KBP_CT_MARKER marker + ldml::string_list ctxt; + auto cp = state->context(); + // We're only interested in as much of the context as is a KM_KBP_CT_CHAR. + // This will stop at the KM_KBP_CT_MARKER type. + uint8_t last_type = KM_KBP_BT_UNKNOWN; + for (auto c = cp.rbegin(); c != cp.rend(); c++) { + last_type = c->type; + if (last_type != KM_KBP_BT_CHAR) { + break; + } + km::kbp::kmx::char16_single buf; + const int len = km::kbp::kmx::Utf32CharToUtf16(c->character, buf); + const std::u16string str(buf.ch, len); + ctxt.push_front(str); // prepend to string + } + if (last_type != KM_KBP_BT_MARKER) { + // There was no beginning-of-translation marker. + //Add one. + state->context().push_marker(0x0); + state->actions().push_marker(0x0); + } + // Look up the key + const std::u16string str = keys.lookup(vk, modifier_state); + if (str.empty()) { + // not found + state->actions().commit(); // finish up and + return KM_KBP_STATUS_OK; // Nothing to do- no key + } + const std::u32string str32 = kmx::u16string_to_u32string(str); + for(size_t i=0; icontext().push_character(str32[i]); + state->actions().push_character(str32[i]); + } + // add the newly added char + ctxt.push_back(str); + // Now process transforms + std::u16string outputString; + // Process the transforms + const size_t matchedContext = transforms.matchContext(ctxt, outputString); + + if (matchedContext > 0) { + // Found something. + // Now, clear out the old context + for (size_t i = 0; i < matchedContext; i++) { + state->context().pop_back(); // Pop off last + auto deletedChar = ctxt[ctxt.size() - i - 1][0]; + state->actions().push_backspace(KM_KBP_BT_CHAR, deletedChar); // Cause prior char to be removed + } + // Now, add in the updated text + const std::u32string outstr32 = kmx::u16string_to_u32string(outputString); + for (size_t i = 0; i < outstr32.length(); i++) { + state->context().push_character(outstr32[i]); + state->actions().push_character(outstr32[i]); + } + // Add a marker so we don't retransform this text. + state->context().push_marker(0x0); + state->actions().push_marker(0x0); + } + } + state->actions().commit(); + } catch (std::bad_alloc &) { + state->actions().clear(); + return KM_KBP_STATUS_NO_MEM; + } + + return KM_KBP_STATUS_OK; +} + +km_kbp_attr const & ldml_processor::attributes() const { + return engine_attrs; +} + +km_kbp_keyboard_key * ldml_processor::get_key_list() const { + km_kbp_keyboard_key* key_list = new km_kbp_keyboard_key(KM_KBP_KEYBOARD_KEY_LIST_END); + return key_list; +} + +km_kbp_keyboard_imx * ldml_processor::get_imx_list() const { + km_kbp_keyboard_imx* imx_list = new km_kbp_keyboard_imx(KM_KBP_KEYBOARD_IMX_END); + return imx_list; +} + +km_kbp_context_item * ldml_processor::get_intermediate_context() { + km_kbp_context_item *citems = new km_kbp_context_item(KM_KBP_CONTEXT_ITEM_END); + return citems; +} + +km_kbp_status ldml_processor::validate() const { + return _valid ? KM_KBP_STATUS_OK : KM_KBP_STATUS_INVALID_KEYBOARD; +} + +} // namespace kbp +} // namespace km diff --git a/core/src/ldml/ldml_processor.hpp b/core/src/ldml/ldml_processor.hpp new file mode 100644 index 00000000000..8a4dfb02e6d --- /dev/null +++ b/core/src/ldml/ldml_processor.hpp @@ -0,0 +1,87 @@ +/* + Copyright: © SIL International. + Description: Internal keyboard class and adaptor class for the LDML Keyboard Processor. + Create Date: 6 Aug 2022 + Authors: Marc Durdin +*/ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include "processor.hpp" +#include "option.hpp" +#include "ldml_vkeys.hpp" +#include "ldml_transforms.hpp" + +namespace km { +namespace kbp { + +#define KM_KBP_LMDL_PROCESSOR_VERSION u"1.0" + + class ldml_processor : public abstract_processor { + private: + bool _valid; + ldml::transforms transforms; + ldml::vkeys keys; + public: + ldml_processor( + path const & kb_path, + const std::vector & data + ); + +// ~ldml_processor() override; + + static bool is_kmxplus_file( + path const & kb_path, + std::vector& data + ); + + km_kbp_status + process_event( + km_kbp_state *state, + km_kbp_virtual_key vk, + uint16_t modifier_state, + uint8_t is_key_down, + uint16_t event_flags + ) override; + + virtual km_kbp_attr const & attributes() const override; + km_kbp_status validate() const override; + + char16_t const * + lookup_option( + km_kbp_option_scope _kmn_unused(scope), + std::u16string const & _kmn_unused(key) + ) const override { + return nullptr; + } + + option + update_option( + km_kbp_option_scope _kmn_unused(scope), + std::u16string const & _kmn_unused(key), + std::u16string const & _kmn_unused(value) + ) override { + return option(); + } + + km_kbp_status process_queued_actions(km_kbp_state *state) override; + + bool queue_action( + km_kbp_state * state, + km_kbp_action_item const* action_item + ) override; + + km_kbp_context_item * get_intermediate_context() override; + + km_kbp_keyboard_key * get_key_list() const override; + + km_kbp_keyboard_imx * get_imx_list() const override; + }; +} // namespace kbp +} // namespace km diff --git a/core/src/ldml/ldml_transforms.cpp b/core/src/ldml/ldml_transforms.cpp new file mode 100644 index 00000000000..86982c79726 --- /dev/null +++ b/core/src/ldml/ldml_transforms.cpp @@ -0,0 +1,69 @@ +/* + Copyright: © SIL International. + Description: This is an implementation of the LDML keyboard spec 3.0. + Create Date: 7 Oct 2022 + Authors: Steven R. Loomis +*/ + +#include "ldml_transforms.hpp" + +namespace km { +namespace kbp { +namespace ldml { + +transforms::transforms() : simple() { +} + +void +transforms::add(const string_list &list, const std::u16string &tostr) { + simple.insert({list, tostr}); +} + +size_t +transforms::matchContext(const string_list &ctxt, std::u16string &outputString) const { + if (simple.size() == 0) { + return 0; // no transforms, no match + } + + // int longest_index = -1; // index to longest match in simple_transforms + size_t longest_length = 0; // length of longest match + // auto i = 0; // counter + // TODO-LDML: need to handle partial matches. + // bool partial = false; // true if the longest was a partial match + for (auto it = simple.begin(); it != simple.end(); it++ /*,i++*/) { + auto list = it->first; + // see if it matches + if (longest_length >= list.size()) { + continue; // already have a longer match + } + if (list.size() > ctxt.size()) { + continue; // not enough ctxt for a match (TODO-LDML: partial match.) + } + // Now, check for match + bool is_match = true; + for (size_t j = 0; j < list.size(); j++) { + const auto found = ctxt.at(ctxt.size() - j - 1); + const auto expect = list.at(list.size() - j - 1); + if (found != expect) { + // mismatch, break + is_match = false; + break; + } + } + if (!is_match) { + continue; + } + // Found a match + longest_length = list.size(); + // longest_index = i; + outputString = it->second; + } + + return longest_length; + // TODO-LDML: reorder + // TODO-LDML: final +} + +} // namespace ldml +} // namespace kbp +} // namespace km diff --git a/core/src/ldml/ldml_transforms.hpp b/core/src/ldml/ldml_transforms.hpp new file mode 100644 index 00000000000..c3edff1f14e --- /dev/null +++ b/core/src/ldml/ldml_transforms.hpp @@ -0,0 +1,54 @@ +/* + Copyright: © SIL International. + Description: Internal functions for LDML transforms + Create Date: 6 Oct 2022 + Authors: Steven R. Loomis +*/ + +#pragma once + +#include +#include +#include +#include +#include + +namespace km { +namespace kbp { +namespace ldml { + +/** + * An ordered list of strings. + */ +typedef std::deque string_list; +/** + * map from transform list to string + */ +typedef std::map simple_transforms; + +class transforms { +private: + simple_transforms simple; + +public: + transforms(); + + /** + * Add a simple transform rule + * @param match list of matching string elements + * @param output output string for this rule + */ + void add(const string_list &match, const std::u16string &output); + + /** + * Check for any matching transform rules + * @param ctxt context array for matching + * @param output fillin: will contain the output string for this rule, if there is a match + * @return 0 if no match, or the length of the ctxt string matched, starting at the end + */ + size_t matchContext(const string_list &ctxt, std::u16string &output) const; +}; + +} // namespace ldml +} // namespace kbp +} // namespace km diff --git a/core/src/ldml/ldml_vkeys.cpp b/core/src/ldml/ldml_vkeys.cpp new file mode 100644 index 00000000000..a2162dc34c2 --- /dev/null +++ b/core/src/ldml/ldml_vkeys.cpp @@ -0,0 +1,37 @@ +/* + Copyright: © SIL International. + Description: Implementation for LDML Vkeys + Create Date: 6 Oct 2022 + Authors: Steven R. Loomis +*/ + +#include "ldml_vkeys.hpp" + +namespace km { +namespace kbp { +namespace ldml { + +vkeys::vkeys() : vkey_to_string() { +} + +void +vkeys::add(km_kbp_virtual_key vk, uint16_t modifier_state, std::u16string output) { + // construct key + const vkey_id id(vk, modifier_state); + // assign the string + vkey_to_string[id] = output; +} + +std::u16string +vkeys::lookup(km_kbp_virtual_key vk, uint16_t modifier_state) const { + const vkey_id id(vk, modifier_state); + const auto key = vkey_to_string.find(id); + if (key == vkey_to_string.end()) { + return std::u16string(); + } + return key->second; +} + +} // namespace ldml +} // namespace kbp +} // namespace km diff --git a/core/src/ldml/ldml_vkeys.hpp b/core/src/ldml/ldml_vkeys.hpp new file mode 100644 index 00000000000..e14284f1066 --- /dev/null +++ b/core/src/ldml/ldml_vkeys.hpp @@ -0,0 +1,52 @@ +/* + Copyright: © SIL International. + Description: Internal functions for LDML vkeys + Create Date: 6 Oct 2022 + Authors: Steven R. Loomis +*/ + +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace km { +namespace kbp { +namespace ldml { + +/** + * identifier for keybag lookup + */ +typedef std::pair vkey_id; + +/** + * LDML Class to manage all things key related: vkey remapping and vkey to string + */ +class vkeys { +private: + // TODO-LDML: store transform=no state + std::map vkey_to_string; + +public: + vkeys(); + + /** + * add a vkey to the bag + */ + void add(km_kbp_virtual_key vk, uint16_t modifier_state, std::u16string output); + + /** + * Lookup a vkey, returns an empty string if not found + */ + std::u16string + lookup(km_kbp_virtual_key vk, uint16_t modifier_state) const; +}; + +} // namespace ldml +} // namespace kbp +} // namespace km diff --git a/core/src/meson.build b/core/src/meson.build index 79f06a97067..8259d0e22a4 100644 --- a/core/src/meson.build +++ b/core/src/meson.build @@ -59,18 +59,33 @@ if compiler.get_id() == 'emscripten' endif kmx_files = files( + 'option.cpp', + 'keyboard.cpp', + 'state.cpp', + 'debuglog.cpp', + 'km_kbp_context_api.cpp', + 'km_kbp_keyboard_api.cpp', + 'km_kbp_options_api.cpp', + 'km_kbp_state_api.cpp', + 'km_kbp_debug_api.cpp', + 'km_kbp_processevent_api.cpp', + 'jsonpp.cpp', + 'ldml/ldml_processor.cpp', + 'ldml/ldml_transforms.cpp', + 'ldml/ldml_vkeys.cpp', + 'mock/mock_processor.cpp', 'kmx/kmx_consts.cpp', 'kmx/kmx_processevent.cpp', 'kmx/kmx_actions.cpp', 'kmx/kmx_capslock.cpp', 'kmx/kmx_context.cpp', 'kmx/kmx_conversion.cpp', - 'kmx/kmx_debug.cpp', 'kmx/kmx_debugger.cpp', 'kmx/kmx_environment.cpp', 'kmx/kmx_file.cpp', 'kmx/kmx_modifiers.cpp', 'kmx/kmx_options.cpp', + 'kmx/kmx_plus.cpp', 'kmx/kmx_processor.cpp', 'kmx/kmx_xstring.cpp', ) diff --git a/core/src/mock/mock_processor.cpp b/core/src/mock/mock_processor.cpp index e2673d83430..183e095b4e4 100644 --- a/core/src/mock/mock_processor.cpp +++ b/core/src/mock/mock_processor.cpp @@ -135,7 +135,8 @@ namespace km { km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state, - uint8_t is_key_down + uint8_t is_key_down, + uint16_t /* event_flags */ ) { assert(state); if (!state) diff --git a/core/src/mock/mock_processor.hpp b/core/src/mock/mock_processor.hpp index a8aa3f1acc3..e5a72cba4a9 100644 --- a/core/src/mock/mock_processor.hpp +++ b/core/src/mock/mock_processor.hpp @@ -31,7 +31,8 @@ namespace kbp km_kbp_state *state, km_kbp_virtual_key vk, uint16_t modifier_state, - uint8_t is_key_down + uint8_t is_key_down, + uint16_t event_flags ) override; virtual km_kbp_attr const & attributes() const override; diff --git a/core/src/path.hpp b/core/src/path.hpp index e13360248ee..9bb8145d741 100644 --- a/core/src/path.hpp +++ b/core/src/path.hpp @@ -59,22 +59,33 @@ namespace kbp template path(C const * p): path(std::basic_string(p)) {} - + /** + * @return the enclosing directory's path + */ path parent() const { auto i = _path.find_last_of(parent_separator); return _path.substr(0, i == string_type::npos ? 0UL : i); } + /** + * @return just the filename, such as "angkor.kmx" + */ path name() const { auto i = _path.find_last_of(parent_separator); return _path.substr(i == string_type::npos ? 0UL : i+1); } + /** + * @return the suffix of the file, such as ".kmx" + */ path suffix() const { auto i = _path.find_last_of(suffix_separator); return _path.substr(i == string_type::npos ? _path.size() : i); } + /** + * @return just the child filename, such as "angkor" for "angkor.kmx" + */ path stem() const { auto psep = _path.find_last_of(parent_separator), ssep = _path.find_last_of(suffix_separator); @@ -82,6 +93,10 @@ namespace kbp ssep == string_type::npos ? _path.size() : ssep); } + /** + * Mutate the path to change the extension + * @param replacement new suffix, such as ".kmp". default is to remove the suffix + */ void replace_extension(path const & replacment = path()) { _path.resize(std::min(_path.find_last_of(suffix_separator), _path.size())); _path += replacment; diff --git a/core/src/processor.hpp b/core/src/processor.hpp index 7041a721005..39edb120d2d 100644 --- a/core/src/processor.hpp +++ b/core/src/processor.hpp @@ -41,7 +41,8 @@ namespace kbp km_kbp_state *, km_kbp_virtual_key, uint16_t modifier_state, - uint8_t is_key_down + uint8_t is_key_down, + uint16_t event_flags ) = 0; virtual km_kbp_status diff --git a/core/tests/unit/kmx/kmx_test_source.cpp b/core/tests/kmx_test_source/kmx_test_source.cpp similarity index 100% rename from core/tests/unit/kmx/kmx_test_source.cpp rename to core/tests/kmx_test_source/kmx_test_source.cpp diff --git a/core/tests/unit/kmx/kmx_test_source.hpp b/core/tests/kmx_test_source/kmx_test_source.hpp similarity index 100% rename from core/tests/unit/kmx/kmx_test_source.hpp rename to core/tests/kmx_test_source/kmx_test_source.hpp diff --git a/core/tests/kmx_test_source/meson.build b/core/tests/kmx_test_source/meson.build new file mode 100644 index 00000000000..4cfdd5661d1 --- /dev/null +++ b/core/tests/kmx_test_source/meson.build @@ -0,0 +1,23 @@ +if compiler.get_id() == 'gcc' or compiler.get_id() == 'clang' or compiler.get_id() == 'emscripten' + warns = [ + '-Wno-missing-field-initializers', + '-Wno-unused-parameter' + ] +else + warns = [] +endif + +coretest_files = files( + 'kmx_test_source.cpp', +) + +kmx_test_source_lib = static_library('kmnkbp-tests', + coretest_files, + cpp_args: defns + warns + flags, + include_directories: [inc, libsrc], + link_args: links, + objects: lib.extract_all_objects(recursive: false), + pic: true, + install: false +) + diff --git a/core/tests/meson.build b/core/tests/meson.build index be58e6975c4..1d37806947f 100644 --- a/core/tests/meson.build +++ b/core/tests/meson.build @@ -12,13 +12,23 @@ stnds = join_paths(meson.current_source_dir(), 'standards') libsrc = include_directories(join_paths('../', 'src')) -if get_option('default_library') != 'static' - ctypes_void_p_size = ['-c', 'import ctypes; print(ctypes.sizeof(ctypes.c_void_p))'] - r = run_command(python, ctypes_void_p_size) - python_ctypes_compatible = r.stdout().to_int() == compiler.sizeof('void *') - if not python_ctypes_compatible - message('Python ctypes is incompatible with built shared object. Disabling some tests.') +# kmx_test_source is required for linux builds, so always enable it even when we +# disable all other tests +subdir('kmx_test_source') + +if get_option('keyman_core_tests') + message('option "keyman_core_tests" is true, enabling tests') + + if get_option('default_library') != 'static' + ctypes_void_p_size = ['-c', 'import ctypes; print(ctypes.sizeof(ctypes.c_void_p))'] + r = run_command(python, ctypes_void_p_size) + python_ctypes_compatible = r.stdout().to_int() == compiler.sizeof('void *') + if not python_ctypes_compatible + message('Python ctypes is incompatible with built shared object. Disabling some tests.') + endif endif -endif -subdir('unit') + subdir('unit') +else + message('option "keyman_core_tests" is false, disabling tests') +endif diff --git a/core/tests/unit/kmnkbd/debug_api.cpp b/core/tests/unit/kmnkbd/debug_api.cpp index 51dcba904c1..b8c5b4c6ecb 100644 --- a/core/tests/unit/kmnkbd/debug_api.cpp +++ b/core/tests/unit/kmnkbd/debug_api.cpp @@ -73,7 +73,7 @@ void setup(const char *keyboard) { void test_debugging_disabled() { setup("000 - null keyboard.kmx"); try_status(km_kbp_state_debug_set(test_state, 0)); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_END} })); @@ -91,7 +91,7 @@ void test_debugging_no_rule_match() { setup("000 - null keyboard.kmx"); DEBUG_GROUP gp = {u"Main"}; try_status(km_kbp_state_debug_set(test_state, 1)); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_S, KM_KBP_MODIFIER_SHIFT, 'S'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -112,7 +112,7 @@ void test_debugging_function_key() { setup("000 - null keyboard.kmx"); DEBUG_GROUP gp = {u"Main"}; try_status(km_kbp_state_debug_set(test_state, 1)); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F1, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F1, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_F1, 0, 0}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -138,7 +138,7 @@ void test_basic_rule_matches() { // 'DE' + 'F' > U+0E04 U+0E05 U+0E06 - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_D, KM_KBP_MODIFIER_SHIFT, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_D, KM_KBP_MODIFIER_SHIFT, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_D, KM_KBP_MODIFIER_SHIFT, 'D'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -151,7 +151,7 @@ void test_basic_rule_matches() { {KM_KBP_IT_END} })); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_E, KM_KBP_MODIFIER_SHIFT, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_E, KM_KBP_MODIFIER_SHIFT, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_E, KM_KBP_MODIFIER_SHIFT, 'E'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -164,7 +164,7 @@ void test_basic_rule_matches() { {KM_KBP_IT_END} })); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F, KM_KBP_MODIFIER_SHIFT, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F, KM_KBP_MODIFIER_SHIFT, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_F, KM_KBP_MODIFIER_SHIFT, 'F'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -208,7 +208,7 @@ void test_multiple_groups() { // '12' -> 'abc' - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_1, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_1, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_1, 0, '1'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -238,7 +238,7 @@ void test_multiple_groups() { {KM_KBP_IT_END} })); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_2, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_2, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_2, 0, '2'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -287,7 +287,7 @@ void test_store_offsets() { // 'ab' -> 'ex' - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_A, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_A, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_A, 0, 'a'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -307,7 +307,7 @@ void test_store_offsets() { {KM_KBP_IT_END} })); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_B, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_B, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_B, 0, 'b'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -361,7 +361,7 @@ void test_set_option() { // '1' -> set_option - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_1, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_1, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_1, 0, '1'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -389,7 +389,7 @@ void test_save_option() { // '2' -> save_option - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_2, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_2, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_2, 0, '2'}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, @@ -424,7 +424,7 @@ void test_backspace_markers() { try_status(km_kbp_state_debug_set(test_state, 1)); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_BKSP, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_BKSP, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(debug_items(test_state, { km_kbp_state_debug_item{KM_KBP_DEBUG_BEGIN, KM_KBP_DEBUG_FLAG_UNICODE, {KM_KBP_VKEY_BKSP, 0, 0}}, km_kbp_state_debug_item{KM_KBP_DEBUG_GROUP_ENTER, 0, {}, {u"", &gp}}, diff --git a/core/tests/unit/kmnkbd/meson.build b/core/tests/unit/kmnkbd/meson.build index 4cdaa91186e..16884298e57 100644 --- a/core/tests/unit/kmnkbd/meson.build +++ b/core/tests/unit/kmnkbd/meson.build @@ -39,7 +39,7 @@ foreach t : tests cpp_args: defns + warns, include_directories: [inc, libsrc], link_args: links + tests_flags, - objects: lib.extract_all_objects()) + objects: lib.extract_all_objects(recursive: false)) test(t[0], bin, args: ['--color', test_path]) endforeach diff --git a/core/tests/unit/kmnkbd/state_api.cpp b/core/tests/unit/kmnkbd/state_api.cpp index 4e338fb21be..37964489b78 100644 --- a/core/tests/unit/kmnkbd/state_api.cpp +++ b/core/tests/unit/kmnkbd/state_api.cpp @@ -156,20 +156,20 @@ int main(int argc, char * argv[]) DISABLE_WARNING_POP try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_S, - KM_KBP_MODIFIER_SHIFT, 1)); + KM_KBP_MODIFIER_SHIFT, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('S')}}, {KM_KBP_IT_END}})); try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_I, - KM_KBP_MODIFIER_SHIFT, 1)); + KM_KBP_MODIFIER_SHIFT, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('I')}}, {KM_KBP_IT_END}})); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('l')}}, {KM_KBP_IT_END}})); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_BKSP, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_BKSP, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(action_items(test_state, {{KM_KBP_IT_BACK, {0,}, {0}}, {KM_KBP_IT_END}})); try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_L, - KM_KBP_MODIFIER_SHIFT, 1)); + KM_KBP_MODIFIER_SHIFT, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(action_items(test_state, {{KM_KBP_IT_CHAR, {0,}, {km_kbp_usv('L')}}, {KM_KBP_IT_END}})); - try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F2, 0, 1)); + try_status(km_kbp_process_event(test_state, KM_KBP_VKEY_F2, 0, 1, KM_KBP_EVENT_FLAG_DEFAULT)); assert(action_items(test_state, {{KM_KBP_IT_PERSIST_OPT, {0,}, {uintptr_t(&expected_persist_opt)}}, {KM_KBP_IT_END}})); diff --git a/core/tests/unit/kmnkbd/test_kmx_xstring.cpp b/core/tests/unit/kmnkbd/test_kmx_xstring.cpp index 104851b7c59..c4b94c52b21 100644 --- a/core/tests/unit/kmnkbd/test_kmx_xstring.cpp +++ b/core/tests/unit/kmnkbd/test_kmx_xstring.cpp @@ -1236,6 +1236,97 @@ test_xstrlen_ignoreifopt() { assert_equal(xstrlen_ignoreifopt((PKMX_WCHAR) U_1F609_WINKING_FACE u"a"), 2); } +void +test_utf32() { + const KMX_DWORD u295 = 0x0127; // ħ + + assert(Uni_IsBMP(u295)); + + const char16_t hmaqtugha = Uni_UTF32BMPToUTF16(u295); + assert(hmaqtugha == 0x0127); + + char16_single c0; + int l0 = Utf32CharToUtf16(u295, c0); + assert(l0 == 1); + assert(c0.ch[0] == 0x0127); + assert(c0.ch[1] == 0); + std::u16string s0 = std::u16string(c0.ch, l0); + assert(s0 == std::u16string(u"ħ")); + assert_equal(s0.at(0), 0x0127); + + const KMX_DWORD scat = 0x0001F640; // 🙀 + assert(!Uni_IsBMP(scat)); + char16_single c1; + int l1 = Utf32CharToUtf16(scat, c1); + assert(l1 == 2); + assert_equal(c1.ch[0], 0xD83D); + assert_equal(c1.ch[1], 0xDE40); + assert_equal(c1.ch[2], 0); + std::u16string s1 = std::u16string(c1.ch, l1); + assert_equal(s1.at(0), 0xD83D); + assert_equal(s1.at(1), 0xDE40); + assert(s1 == std::u16string(u"🙀")); +} + +void +test_u16string_to_u32string() { + // normal cases + { + const std::u32string str = u16string_to_u32string(u""); + assert_equal(str.length(), 0); + } + { + const std::u32string str = u16string_to_u32string(u"e"); + assert_equal(str.length(), 1); + assert_equal(str.at(0), 0x0065); + } + { + const std::u32string str = u16string_to_u32string(u"🙀"); + assert_equal(str.length(), 1); + assert_equal(str.at(0), 0x0001F640); + } + { + const std::u32string str = u16string_to_u32string(u"Ω🙀"); + assert_equal(str.length(), 2); + assert_equal(str.at(0), u'Ω'); + assert_equal(str.at(1), 0x0001F640); + } + + // error cases + { + std::u16string half_cat; + half_cat.push_back(0xD83D); // mismatched lead surrogate + const std::u32string str = u16string_to_u32string(half_cat); + assert_equal(str.length(), 1); + assert_equal(str.at(0), 0xFFFD); + } + { + std::u16string half_cat; + half_cat.push_back(0xD83D); // mismatched lead surrogate + half_cat.push_back(u'Ω'); // with following text + const std::u32string str = u16string_to_u32string(half_cat); + assert_equal(str.length(), 2); + assert_equal(str.at(0), 0xFFFD); + assert_equal(str.at(1), u'Ω'); + } + { + std::u16string half_cat; + half_cat.push_back(0xDE40); // mismatched trail surrogate + const std::u32string str = u16string_to_u32string(half_cat); + assert_equal(str.length(), 1); + assert_equal(str.at(0), 0xFFFD); + } + { + std::u16string no_cat; + no_cat.push_back(0xDE40); + no_cat.push_back(0xD83D); + const std::u32string str = u16string_to_u32string(no_cat); + assert_equal(str.length(), 2); + assert_equal(str.at(0), 0xFFFD); + assert_equal(str.at(1), 0xFFFD); + } +} + constexpr const auto help_str = u"\ test_kmx_xstring [--color]\n\ \n\ @@ -1256,6 +1347,8 @@ int main(int argc, char *argv []) { test_decxstr(); test_xstrlen(); test_xstrlen_ignoreifopt(); + test_utf32(); + test_u16string_to_u32string(); return 0; } diff --git a/core/tests/unit/kmx/fixtures/binary/b_000_zero_checksum.kmn b/core/tests/unit/kmx/fixtures/binary/b_000_zero_checksum.kmn new file mode 100644 index 00000000000..ef18008b88c --- /dev/null +++ b/core/tests/unit/kmx/fixtures/binary/b_000_zero_checksum.kmn @@ -0,0 +1,7 @@ +c Description: see b_000_zero_checksum.txt +c keys: abc +c expected: abc +c context: + +begin Unicode > use(main) +group(main) using keys \ No newline at end of file diff --git a/core/tests/unit/kmx/fixtures/binary/b_000_zero_checksum.txt b/core/tests/unit/kmx/fixtures/binary/b_000_zero_checksum.txt new file mode 100644 index 00000000000..c8c6084447c --- /dev/null +++ b/core/tests/unit/kmx/fixtures/binary/b_000_zero_checksum.txt @@ -0,0 +1,43 @@ +# +# b_000_zero_checksum.txt is a version 16.0 file with a zero checksum, to verify +# that Keyman Core loads it successfully. +# + +block(kmxheader) # struct COMP_KEYBOARD { + 4b 58 54 53 # KMX_DWORD dwIdentifier; // 0000 Keyman compiled keyboard id + + 00 10 00 00 # KMX_DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400 + + 00 00 00 00 # KMX_DWORD dwCheckSum; // 0008 As stored in keyboard + 00 00 00 00 # KMX_DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts + 01 00 00 00 # KMX_DWORD IsRegistered; // 0010 + 00 00 00 00 # KMX_DWORD version; // 0014 keyboard version + + 00 00 00 00 # KMX_DWORD cxStoreArray; // 0018 in array entries + 01 00 00 00 # KMX_DWORD cxGroupArray; // 001C in array entries + + 00 00 00 00 # KMX_DWORD dpStoreArray; // 0020 [LPSTORE] address of first item in store array + 40 00 00 00 # KMX_DWORD dpGroupArray; // 0024 [LPGROUP] address of first item in group array + + ff ff ff ff # KMX_DWORD StartGroup[2]; // 0028 index of starting groups [2 of them] + 00 00 00 00 # + + 00 00 00 00 # KMX_DWORD dwFlags; // 0030 Flags for the keyboard file + + 00 00 00 00 # KMX_DWORD dwHotKey; // 0034 standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) + + 00 00 00 00 # KMX_DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file + 00 00 00 00 # KMX_DWORD dwBitmapSize; // 003C size in bytes of the bitmaps + # }; + +block(groupmain) + # struct COMP_GROUP { + 00 00 00 00 # KMX_DWORD dpName; + 00 00 00 00 # KMX_DWORD dpKeyArray; // [LPKEY] address of first item in key array + 00 00 00 00 # KMX_DWORD dpMatch; + 00 00 00 00 # KMX_DWORD dpNoMatch; + 00 00 00 00 # KMX_DWORD cxKeyArray; // in array entries + 01 00 00 00 # KMX_BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not + # }; + +block(eof) # end of file \ No newline at end of file diff --git a/core/tests/unit/kmx/fixtures/binary/meson.build b/core/tests/unit/kmx/fixtures/binary/meson.build new file mode 100644 index 00000000000..5998a3b1e96 --- /dev/null +++ b/core/tests/unit/kmx/fixtures/binary/meson.build @@ -0,0 +1,42 @@ +# Copyright: © 2018 SIL International. +# Description: Cross platform build script to compile libkmnkbp API unit tests. +# Create Date: 19 Oct 2018 +# Authors: Marc Durdin, Tim Eves (TSE) +# History: 19 Oct 2018 - TSE - Added test for context API functions. +# + +# binary file unit tests + +# Build all binary test keyboards + +if compiler.get_id() == 'emscripten' + binary_test_path = '/' +else + binary_test_path = meson.current_build_dir() +endif + +binary_tests = [ + 'b_000_zero_checksum' +] + +foreach kbd : binary_tests + configure_file( + command: copy_cmd + ['@INPUT@', '@OUTPUT@'], + input: kbd + '.kmn', + output: kbd + '.kmn' + ) + + configure_file( + command: hextobin_cmd + ['@INPUT@', '@OUTPUT@'], + output: kbd + '.kmx', + input: kbd + '.txt' + ) +endforeach + +# Load all binary test keyboards + +foreach kbd : binary_tests + kbd_src = join_paths(binary_test_path, kbd) + '.kmn' + kbd_obj = join_paths(binary_test_path, kbd) + '.kmx' + test(kbd, kmx, args: [kbd_src, kbd_obj]) +endforeach diff --git a/core/tests/unit/kmx/fixtures/meson.build b/core/tests/unit/kmx/fixtures/meson.build new file mode 100644 index 00000000000..e40c30ad462 --- /dev/null +++ b/core/tests/unit/kmx/fixtures/meson.build @@ -0,0 +1,6 @@ +if node.found() + # Note: if node is not available, we cannot build the keyboards; build.sh + # emits a warning that the 'ldml' keyboard tests will be skipped; that + # includes these tests for now + subdir('binary') +endif \ No newline at end of file diff --git a/core/tests/unit/kmx/kmx.cpp b/core/tests/unit/kmx/kmx.cpp index 09397639f57..8f91844fbb7 100644 --- a/core/tests/unit/kmx/kmx.cpp +++ b/core/tests/unit/kmx/kmx.cpp @@ -78,11 +78,12 @@ apply_action( 0, }, {act.character}}); - if (Uni_IsSMP(act.character)) { - text_store.push_back(Uni_UTF32ToSurrogate1(act.character)); - text_store.push_back(Uni_UTF32ToSurrogate2(act.character)); - } else { - text_store.push_back((char16_t)act.character); + { + km::kbp::kmx::char16_single buf; + const int len = km::kbp::kmx::Utf32CharToUtf16(act.character, buf); + for(int i=0; i + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/fr-t-k0-azerty.xml b/core/tests/unit/ldml/keyboards/fr-t-k0-azerty.xml new file mode 100644 index 00000000000..d890590e5a6 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/fr-t-k0-azerty.xml @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_000_null_keyboard.xml b/core/tests/unit/ldml/keyboards/k_000_null_keyboard.xml new file mode 100644 index 00000000000..516c336b247 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_000_null_keyboard.xml @@ -0,0 +1,10 @@ + + diff --git a/core/tests/unit/ldml/keyboards/k_001_tiny-test.xml b/core/tests/unit/ldml/keyboards/k_001_tiny-test.xml new file mode 100644 index 00000000000..43310b71f8e --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_001_tiny-test.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_001_tiny.xml b/core/tests/unit/ldml/keyboards/k_001_tiny.xml new file mode 100644 index 00000000000..998599ede5b --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_001_tiny.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_002_tinyu32.xml b/core/tests/unit/ldml/keyboards/k_002_tinyu32.xml new file mode 100644 index 00000000000..f6b59e89a96 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_002_tinyu32.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_003_transform.xml b/core/tests/unit/ldml/keyboards/k_003_transform.xml new file mode 100644 index 00000000000..4002fa2c921 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_003_transform.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml b/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml new file mode 100644 index 00000000000..a900da9efe4 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_004_tinyshift.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_010_mt.xml b/core/tests/unit/ldml/keyboards/k_010_mt.xml new file mode 100644 index 00000000000..ba3e6ab5dbd --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_010_mt.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_011_mt_iso.xml b/core/tests/unit/ldml/keyboards/k_011_mt_iso.xml new file mode 100644 index 00000000000..2b89cef958a --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_011_mt_iso.xml @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_100_keytest.xml b/core/tests/unit/ldml/keyboards/k_100_keytest.xml new file mode 100644 index 00000000000..d185035c940 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_100_keytest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_101_keytest.xml b/core/tests/unit/ldml/keyboards/k_101_keytest.xml new file mode 100644 index 00000000000..16e4c76f91a --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_101_keytest.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/k_102_keytest.xml b/core/tests/unit/ldml/keyboards/k_102_keytest.xml new file mode 100644 index 00000000000..45ab4b71c0f --- /dev/null +++ b/core/tests/unit/ldml/keyboards/k_102_keytest.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + diff --git a/core/tests/unit/ldml/keyboards/meson.build b/core/tests/unit/ldml/keyboards/meson.build new file mode 100644 index 00000000000..ced7bb921b9 --- /dev/null +++ b/core/tests/unit/ldml/keyboards/meson.build @@ -0,0 +1,57 @@ +# Copyright: © SIL International. +# Description: Cross platform build script to compile kmkbpldml API unit tests. +# Create Date: 5 Aug 2022 +# Authors: Marc Durdin +# + + +# These tests have a k_001_tiny-test.xml file as well. + +tests_with_testdata = [ + 'k_001_tiny', + 'fr-t-k0-azerty', +] + +tests = [ +# disabling 000 until we have updates to core or to the keyboard so that it passes +# 'k_000_null_keyboard', + 'k_002_tinyu32', + 'k_003_transform', + 'k_004_tinyshift', + 'k_010_mt', + 'k_011_mt_iso', + 'k_100_keytest', + 'k_101_keytest', + 'k_102_keytest', +] + +tests += tests_with_testdata + +# Setup kmc + +kmc_root = join_paths(meson.source_root(),'..','developer','src','kmc') +kmc_cmd = [node, kmc_root] + +# Build all keyboards in output folder + +foreach kbd : tests + configure_file( + command: copy_cmd + ['@INPUT@', '@OUTPUT@'], + input: kbd + '.xml', + output: kbd + '.xml' + ) + + configure_file( + command: kmc_cmd + ['build', '@INPUT@', '--out-file', '@OUTPUT@'], + output: kbd + '.kmx', + input: kbd + '.xml' + ) +endforeach + +foreach kbd : tests_with_testdata + configure_file( + command: kmc_cmd + ['build-test-data', '@INPUT@', '--out-file', '@OUTPUT@'], + output: kbd + '-test.json', + input: kbd + '-test.xml' + ) +endforeach diff --git a/core/tests/unit/ldml/ldml.cpp b/core/tests/unit/ldml/ldml.cpp new file mode 100644 index 00000000000..d651305d3cd --- /dev/null +++ b/core/tests/unit/ldml/ldml.cpp @@ -0,0 +1,395 @@ +/* + Copyright: © SIL International. + Description: Tests for LDML keyboard integration + Create Date: 5 Aug 2022 + Authors: Marc Durdin (MD) + + Note: Exit codes will be 100*LINE + ERROR CODE, e.g. 25005 is code 5 on line 250 +*/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "path.hpp" +#include "state.hpp" +#include "utfcodec.hpp" + +#include "../test_assert.h" +#include "../test_color.h" + +#include // for surrogate pair macros + +#include "ldml_test_source.hpp" + +namespace { + +bool g_beep_found = false; + +km_kbp_option_item test_env_opts[] = +{ + KM_KBP_OPTIONS_END +}; + +std::string +string_to_hex(const std::u16string &input) { + std::ostringstream result; + result << std::setfill('0') << std::hex << std::uppercase; + + for (size_t i = 0; i < input.length(); i++) { + unsigned int ch = input[i]; + if (i < input.length() - 1 && Uni_IsSurrogate1(input[i]) && Uni_IsSurrogate2(input[i + 1])) { + ch = Uni_SurrogateToUTF32(input[i], input[i + 1]); + i++; + } + + result << "U+" << std::setw(4) << ch << " "; + } + return result.str(); +} + +void +apply_action( + km_kbp_state const *, + km_kbp_action_item const &act, + std::u16string &text_store, + std::vector &context, + km::tests::LdmlTestSource &test_source) { + switch (act.type) { + case KM_KBP_IT_END: + assert(false); + break; + case KM_KBP_IT_ALERT: + g_beep_found = true; + // std::cout << "beep" << std::endl; + break; + case KM_KBP_IT_CHAR: + context.push_back(km_kbp_context_item{ + KM_KBP_CT_CHAR, + { + 0, + }, + {act.character}}); + { + km::kbp::kmx::char16_single buf; + const int len = km::kbp::kmx::Utf32CharToUtf16(act.character, buf); + for(int i=0; i 0) { + assert(!context.empty() && !text_store.empty()); + km_kbp_usv ch = text_store.back(); + text_store.pop_back(); + if (text_store.length() > 0 && Uni_IsSurrogate2(ch)) { + auto ch1 = text_store.back(); + if (Uni_IsSurrogate1(ch1)) { + // We'll only pop the next character off it is actually a + // surrogate pair + ch = Uni_SurrogateToUTF32(ch1, ch); + text_store.pop_back(); + } + } + assert(ch == act.backspace.expected_value); + + assert(context.back().type == KM_KBP_CT_CHAR); + assert(context.back().character == ch); + context.pop_back(); + } + break; + case KM_KBP_IT_PERSIST_OPT: + break; + case KM_KBP_IT_INVALIDATE_CONTEXT: + std::cout << "action: context invalidated (markers cleared)" << std::endl; + break; + case KM_KBP_IT_EMIT_KEYSTROKE: + std::cout << "action: emit keystroke" << std::endl; + break; + case KM_KBP_IT_CAPSLOCK: + std::cout << "action: capsLock " << act.capsLock << std::endl; + test_source.set_caps_lock_on(act.capsLock); + break; + default: + assert(false); // NOT SUPPORTED + break; + } +} + +/** + * verify the current context +*/ +void +verify_context(std::u16string& text_store, km_kbp_state* &test_state, std::vector &test_context) { + // Compare context and text store at each step - should be identical + size_t n = 0; + km_kbp_context_item* citems = nullptr; + try_status(km_kbp_context_get(km_kbp_state_context(test_state), &citems)); + try_status(km_kbp_context_items_to_utf16(citems, nullptr, &n)); + km_kbp_cp *buf = new km_kbp_cp[n]; + try_status(km_kbp_context_items_to_utf16(citems, buf, &n)); + std::cout << "context : " << string_to_hex(buf) << " [" << buf << "]" << std::endl; + + // Verify that both our local test_context and the core's test_state.context have + // not diverged + auto ci = citems; + for (auto test_ci = test_context.begin(); ci->type != KM_KBP_CT_END || test_ci != test_context.end(); ci++, test_ci++) { + assert(ci->type != KM_KBP_CT_END && test_ci != test_context.end()); // Verify that both lists are same length + assert(test_ci->type == ci->type && test_ci->marker == ci->marker); + } + + km_kbp_context_items_dispose(citems); + if (text_store != buf) { + std::cerr << "text store has diverged from buf" << std::endl; + std::cerr << "text store: " << string_to_hex(text_store) << " [" << text_store << "]" << std::endl; + assert(false); + } + delete [] buf; + +} + +int +run_test(const km::kbp::path &source, const km::kbp::path &compiled, km::tests::LdmlTestSource& test_source) { + km_kbp_keyboard * test_kb = nullptr; + km_kbp_state * test_state = nullptr; + + const km_kbp_status expect_load_status = test_source.get_expected_load_status(); + assert_equal(km_kbp_keyboard_load(compiled.c_str(), &test_kb), expect_load_status); + + if (expect_load_status != KM_KBP_STATUS_OK) { + std::cout << "Keyboard was expected to be invalid, so exiting " << std::endl; + return 0; + } + + // Setup state, environment + try_status(km_kbp_state_create(test_kb, test_env_opts, &test_state)); + + // Setup context + km_kbp_context_item *citems = nullptr; + try_status(km_kbp_context_items_from_utf16(test_source.get_context().c_str(), &citems)); + try_status(km_kbp_context_set(km_kbp_state_context(test_state), citems)); + + // Make a copy of the setup context for the test + std::vector test_context; + for(km_kbp_context_item *ci = citems; ci->type != KM_KBP_CT_END; ci++) { + test_context.emplace_back(*ci); + } + km_kbp_context_items_dispose(citems); + + // Setup baseline text store + std::u16string text_store = test_source.get_context(); + + km::tests::ldml_action action; + + // verify at beginning + verify_context(text_store, test_state, test_context); + + // Run through actions, applying output for each event + for (test_source.next_action(action); action.type != km::tests::LDML_ACTION_DONE; test_source.next_action(action)) { + if (action.type == km::tests::LDML_ACTION_KEY_EVENT) { + auto &p = action.k; + std::cout << "- key action: 0x" << std::hex << p.vk << "/modifier 0x" << p.modifier_state << std::dec << std::endl; + // Because a normal system tracks caps lock state itself, + // we mimic that in the tests. We assume caps lock state is + // updated on key_down before the processor receives the + // event. + if (p.vk == KM_KBP_VKEY_CAPS) { + test_source.toggle_caps_lock_state(); + } + + for (auto key_down = 1; key_down >= 0; key_down--) { + // expected error only applies to key down + try_status(km_kbp_process_event(test_state, p.vk, p.modifier_state | test_source.caps_lock_state(), key_down, KM_KBP_EVENT_FLAG_DEFAULT)); // TODO-LDML: for now. Should send touch and hardware events. + + for (auto act = km_kbp_state_action_items(test_state, nullptr); act->type != KM_KBP_IT_END; act++) { + apply_action(test_state, *act, text_store, test_context, test_source); + } + } + verify_context(text_store, test_state, test_context); + } else if (action.type == km::tests::LDML_ACTION_EMIT_STRING) { + std::cout << "- string emit action: " << action.string << std::endl; + std::cerr << "TODO-LDML: note, LDML_ACTION_EMIT_STRING is NOT going through keyboard, transforms etc." << std::endl; + text_store.append(action.string); // TODO-LDML: not going through keyboard + // Now, update context? + km_kbp_context_item *nitems = nullptr; + try_status(km_kbp_context_items_from_utf16(action.string.c_str(), &nitems)); + try_status(km_kbp_context_append(km_kbp_state_context(test_state), nitems)); + // update the test_context also. + for (km_kbp_context_item *ci = nitems; ci->type != KM_KBP_CT_END; ci++) { + test_context.emplace_back(*ci); + } + km_kbp_context_items_dispose(nitems); + + verify_context(text_store, test_state, test_context); + } else if (action.type == km::tests::LDML_ACTION_CHECK_EXPECTED) { + std::cout << "- check expected" << std::endl; + std::cout << "expected : " << string_to_hex(action.string) << " [" << action.string << "]" << std::endl; + std::cout << "text store: " << string_to_hex(text_store) << " [" << text_store << "]" << std::endl; + // Compare internal context with expected result + if (text_store != action.string) return __LINE__; + } + } + + // Test if the beep action was as expected + if (g_beep_found != test_source.get_expected_beep()) + return __LINE__; + + + // re-verify at end. + verify_context(text_store, test_state, test_context); + + // Verify that both our local test_context and the core's test_state.context have + // not diverged + try_status(km_kbp_context_get(km_kbp_state_context(test_state), &citems)); + auto ci = citems; + for(auto test_ci = test_context.begin(); ci->type != KM_KBP_CT_END || test_ci != test_context.end(); ci++, test_ci++) { + assert(ci->type != KM_KBP_CT_END && test_ci != test_context.end()); // Verify that both lists are same length + assert(test_ci->type == ci->type && test_ci->marker == ci->marker); + } + + km_kbp_context_items_dispose(citems); + + // Destroy them + km_kbp_state_dispose(test_state); + km_kbp_keyboard_dispose(test_kb); + + return 0; +} + +/** + * Run all tests for this keyboard + */ +int run_all_tests(const km::kbp::path &source, const km::kbp::path &compiled) { + std::cout << "source file = " << source << std::endl + << "compiled file = " << compiled << std::endl; + + km::tests::LdmlEmbeddedTestSource embedded_test_source; + + int embedded_result = embedded_test_source.load_source(source); + + if (embedded_result == 0) { + // embedded loaded OK, try it + std::cout << "TEST: " << source.name() << " (embedded)" << std::endl; + embedded_result = run_test(source, compiled, embedded_test_source); + } else { + embedded_result = -1; // load failed + } + + km::tests::LdmlJsonTestSourceFactory json_factory; + // adjust path + + const auto json_path = km::tests::LdmlJsonTestSourceFactory::kmx_to_test_json(compiled); + int json_result = json_factory.load(compiled, json_path); + if (json_result != -1) { + const km::tests::JsonTestMap& json_tests = json_factory.get_tests(); + + assert(json_tests.size() > 0); + // Loop over all tests + for (const auto& n : json_tests) { + std::cout << "TEST: " << json_path.stem() << "/" << n.first << std::endl; + int sub_test = run_test(source, compiled, *n.second); + if (sub_test != 0) { + std::cout << " FAIL: " << json_path.stem() << "/" << n.first << std::endl; + json_result = sub_test; // set to last failure + } else { + std::cout << " PASS: " << json_path.stem() << "/" << n.first << std::endl; + } + } + std::cout << " " << json_tests.size() << " JSON test(s) in " << json_path.stem() << std::endl; + } + + + // OK. + if (embedded_result == -1) { + std::cout << "Note: No embedded test." << std::endl; + } + if (json_result == -1) { + std::cout << "Note: No json test." << std::endl; + } + + if (embedded_result == -1 && json_result == -1) { + // Can't both be missing. + std::cout << "Error: Need either embedded test (@@ directives in " << source.name() << ") or " << json_path.name() << std::endl; + return __LINE__; + } else if (embedded_result == -1) { + return json_result; // Return JSON if embedded missing. + } else if (json_result == -1) { + return embedded_result; // Return embedded if JSON missing + } + + // we have both tests. + if (embedded_result == 0) { + return json_result; // Both passed or JSON failed + } else if(json_result == 0) { + return embedded_result; // Embedded may have failed. + } else { + return json_result; + } +} + + + +constexpr const auto help_str = + "\ +ldml [--color] \n\ +help:\n\ +\tKMN_FILE:\tThe ldml test file for the keyboard under test.\n\ +\tKMX_FILE:\tThe corresponding compiled kmx file.\n"; + +} // namespace + +int error_args() { + std::cerr << "ldml: Not enough arguments." << std::endl; + std::cout << help_str; + return 1; +} + +int main(int argc, char *argv[]) { + int first_arg = 1; + + if (argc < 3) { + return error_args(); + } + + auto arg_color = std::string(argv[1]) == "--color"; + if(arg_color) { + first_arg++; + if(argc < 4) { + return error_args(); + } + } + console_color::enabled = console_color::isaterminal() || arg_color; + + int rc = run_all_tests(argv[first_arg], argv[first_arg + 1]); + if (rc != 0) { + std::cerr << "FAILED" << std::endl; + } + return rc; +} diff --git a/core/tests/unit/ldml/ldml_test_source.cpp b/core/tests/unit/ldml/ldml_test_source.cpp new file mode 100644 index 00000000000..37e10ee48eb --- /dev/null +++ b/core/tests/unit/ldml/ldml_test_source.cpp @@ -0,0 +1,560 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if 0 +// TODO-LDML If we need to avoid exceptions in JSON +#define JSON_TRY_USER if(true) +#define JSON_CATCH_USER(exception) if(false) +#define JSON_THROW_USER(exception) { return __LINE__; } // get out +#endif + +#include + +#include // for char to vk mapping tables +#include // for surrogate pair macros +#include +#include "ldml/keyboardprocessor_ldml.h" +#include "ldml/ldml_processor.hpp" + + +#include "path.hpp" +#include "state.hpp" +#include "utfcodec.hpp" + +#include "ldml_test_source.hpp" + +#define assert_or_return(expr) if(!(expr)) { \ + std::wcerr << __FILE__ << ":" << __LINE__ << ": " << \ + console_color::fg(console_color::BRIGHT_RED) \ + << "warning: " << (#expr) \ + << console_color::reset() \ + << std::endl; \ + return __LINE__; \ +} + +#define TEST_JSON_SUFFIX "-test.json" +namespace km { +namespace tests { + +#include "../test_color.h" + + +LdmlTestSource::LdmlTestSource() { +} + + +LdmlTestSource::~LdmlTestSource() { + +} + +km_kbp_status LdmlTestSource::get_expected_load_status() { + return KM_KBP_STATUS_OK; +} + +bool LdmlTestSource::get_expected_beep() const { + return false; +} + +// String trim functions from https://stackoverflow.com/a/217605/1836776 +// trim from start (in place) +static inline void +ltrim(std::string &s) { + s.erase(s.begin(), std::find_if(s.begin(), s.end(), [](int ch) { return !std::isspace(ch); })); +} + +// trim from end (in place) +static inline void +rtrim(std::string &s) { + s.erase(std::find_if(s.rbegin(), s.rend(), [](int ch) { return !std::isspace(ch); }).base(), s.end()); +} + +// trim from both ends (in place) +static inline void +trim(std::string &s) { + ltrim(s); + rtrim(s); +} + +LdmlEmbeddedTestSource::LdmlEmbeddedTestSource() { + +} + +LdmlEmbeddedTestSource::~LdmlEmbeddedTestSource() { + +} + +std::u16string +LdmlTestSource::parse_source_string(std::string const &s) { + std::u16string t; + for (auto p = s.begin(); p != s.end(); p++) { + if (*p == '\\') { + p++; + km_kbp_usv v; + assert(p != s.end()); + if (*p == 'u' || *p == 'U') { + // Unicode value + p++; + size_t n; + std::string s1 = s.substr(p - s.begin(), 8); + v = std::stoul(s1, &n, 16); + // Allow deadkey_number (U+0001) characters and onward + assert(v >= 0x0001 && v <= 0x10FFFF); + p += n - 1; + if (v < 0x10000) { + t += km_kbp_cp(v); + } else { + t += km_kbp_cp(Uni_UTF32ToSurrogate1(v)); + t += km_kbp_cp(Uni_UTF32ToSurrogate2(v)); + } + } else if (*p == 'd') { + // Deadkey + // TODO, not yet supported + assert(false); + } + } else { + t += *p; + } + } + return t; +} + +std::u16string +LdmlTestSource::parse_u8_source_string(std::string const &u8s) { + // convert from utf-8 to utf-16 first + std::u16string s = std::wstring_convert, char16_t>{}.from_bytes(u8s); + std::u16string t; + for (auto p = s.begin(); p != s.end(); p++) { + if (*p == '\\') { + p++; + km_kbp_usv v; + assert(p != s.end()); + if (*p == 'u' || *p == 'U') { + // Unicode value + p++; + size_t n; + std::u16string s1 = s.substr(p - s.begin(), 8); + // TODO-LDML: convert back first? + std::string s1b = std::wstring_convert, char16_t>{}.to_bytes(s1); + v = std::stoul(s1b, &n, 16); + // Allow deadkey_number (U+0001) characters and onward + assert(v >= 0x0001 && v <= 0x10FFFF); + p += n - 1; + if (v < 0x10000) { + t += km_kbp_cp(v); + } else { + t += km_kbp_cp(Uni_UTF32ToSurrogate1(v)); + t += km_kbp_cp(Uni_UTF32ToSurrogate2(v)); + } + } else if (*p == 'd') { + // Deadkey + // TODO, not yet supported + assert(false); + } + } else { + t += *p; + } + } + return t; +} + +bool +LdmlEmbeddedTestSource::is_token(const std::string token, std::string &line) { + if (line.compare(0, token.length(), token) == 0) { + line = line.substr(token.length()); + trim(line); + return true; + } + return false; +} + +int +LdmlEmbeddedTestSource::load_source( const km::kbp::path &path ) { + const std::string s_keys = "@@keys: "; + const std::string s_expected = "@@expected: "; + const std::string s_context = "@@context: "; + const std::string s_capsLock = "@@capsLock: "; + const std::string s_expecterror = "@@expect-error: "; + + // Parse out the header statements in file.kmn that tell us (a) environment, (b) key sequence, (c) start context, (d) expected + // result + std::ifstream kmn(path.native()); + if (!kmn.good()) { + std::cerr << "could not open file: " << path << std::endl; + return __LINE__; + } + std::string line; + while (std::getline(kmn, line)) { + trim(line); + + if (!line.length()) + continue; + if (line.compare(0, s_keys.length(), s_keys) == 0) { + keys = line.substr(s_keys.length()); + trim(keys); + } else if (is_token(s_expected, line)) { + if (line == "\\b") { + expected_beep = true; + } else { + expected = parse_source_string(line); + } + } else if (is_token(s_expecterror, line)) { + expected_error = true; + } else if (is_token(s_context, line)) { + context = parse_source_string(line); + } else if (is_token(s_capsLock, line)) { + set_caps_lock_on(parse_source_string(line).compare(u"1") == 0); + } + } + + if (keys == "") { + // We must at least have a key sequence to run the test + return __LINE__; + } + + return 0; +} + +km_kbp_status +LdmlEmbeddedTestSource::get_expected_load_status() { + return expected_error ? KM_KBP_STATUS_INVALID_KEYBOARD : KM_KBP_STATUS_OK; +} + +const std::u16string& +LdmlEmbeddedTestSource::get_context() const { + return context; +} + +bool LdmlEmbeddedTestSource::get_expected_beep() const { + return expected_beep; +} + +int +LdmlTestSource::caps_lock_state() { + return _caps_lock_on ? KM_KBP_MODIFIER_CAPS : 0; +} + +void +LdmlTestSource::toggle_caps_lock_state() { + _caps_lock_on = !_caps_lock_on; +} + +void +LdmlTestSource::set_caps_lock_on(bool caps_lock_on) { + _caps_lock_on = caps_lock_on; +} + +key_event +LdmlTestSource::char_to_event(char ch) { + assert(ch >= 32); + return { + km::kbp::kmx::s_char_to_vkey[(int)ch - 32].vk, + (uint16_t)(km::kbp::kmx::s_char_to_vkey[(int)ch - 32].shifted ? KM_KBP_MODIFIER_SHIFT : 0)}; +} + +uint16_t +LdmlTestSource::get_modifier(std::string const m) { + for (int i = 0; km::kbp::kmx::s_modifier_names[i].name; i++) { + if (m == km::kbp::kmx::s_modifier_names[i].name) { + return km::kbp::kmx::s_modifier_names[i].modifier; + } + } + return 0; +} + +km_kbp_virtual_key +LdmlTestSource::get_vk(std::string const &vk) { + for (int i = 1; i < 256; i++) { + if (vk == km::kbp::kmx::s_key_names[i]) { + return i; + } + } + return 0; +} + +key_event +LdmlEmbeddedTestSource::vkey_to_event(std::string const &vk_event) { + // vkey format is MODIFIER MODIFIER K_NAME + // std::cout << "VK=" << vk_event << std::endl; + + std::stringstream f(vk_event); + std::string s; + uint16_t modifier_state = 0; + km_kbp_virtual_key vk = 0; + while (std::getline(f, s, ' ')) { + uint16_t modifier = get_modifier(s); + if (modifier != 0) { + modifier_state |= modifier; + } else { + vk = get_vk(s); + break; + } + } + + // The string should be empty at this point + assert(!std::getline(f, s, ' ')); + assert(vk != 0); + + return {vk, modifier_state}; +} + +void +LdmlEmbeddedTestSource::next_action(ldml_action &fillin) { + if (is_done) { + // We were already done. return done. + fillin.type = LDML_ACTION_DONE; + return; + } else if(keys.empty()) { + // Got to the end of the keys. time to check + fillin.type = LDML_ACTION_CHECK_EXPECTED; + fillin.string = expected; // copy expected + is_done = true; // so we get DONE next time + } else { + fillin.type = LDML_ACTION_KEY_EVENT; + fillin.k = next_key(); + } +} + + +key_event +LdmlEmbeddedTestSource::next_key() { + // mutate this->keys + return next_key(keys); +} + +key_event +LdmlEmbeddedTestSource::next_key(std::string &keys) { + // Parse the next element of the string, chop it off, and return it + // mutates keys + if (keys.length() == 0) + return {0, 0}; + char ch = keys[0]; + if (ch == '[') { + if (keys.length() > 1 && keys[1] == '[') { + keys.erase(0, 2); + return char_to_event(ch); + } + auto n = keys.find(']'); + assert(n != std::string::npos); + auto vkey = keys.substr(1, n - 1); + keys.erase(0, n + 1); + return vkey_to_event(vkey); + } else { + keys.erase(0, 1); + return char_to_event(ch); + } +} + + +class LdmlJsonTestSource : public LdmlTestSource { +public: + LdmlJsonTestSource(const std::string &path, km::kbp::kmx::kmx_plus *kmxplus); + virtual ~LdmlJsonTestSource(); + virtual const std::u16string &get_context() const; + int load(const nlohmann::json &test); + virtual void next_action(ldml_action &fillin); +private: + std::string path; + nlohmann::json data; // maybe + std::u16string context; + std::u16string expected; + /** + * Which action are we on? + */ + std::size_t action_index = -1; + const km::kbp::kmx::kmx_plus *kmxplus; + /** + * Helpers + */ + void set_key_from_id(key_event& k, const std::u16string& id); +}; + +LdmlJsonTestSource::LdmlJsonTestSource(const std::string &path, km::kbp::kmx::kmx_plus *k) +:path(path), kmxplus(k) { + +} + +LdmlJsonTestSource::~LdmlJsonTestSource() { +} + +void LdmlJsonTestSource::set_key_from_id(key_event& k, const std::u16string& id) { + k = {0, 0}; // set to a null value at first. + + assert(kmxplus != nullptr); + // lookup the id + assert(kmxplus->key2 != nullptr); + + assert(kmxplus->key2Helper.valid()); + + // TODO-LDML: optimize. or optimise. + + // First, find the string + KMX_DWORD strId = kmxplus->strs->find(id); + assert(strId != 0); + if (strId == 0) { // will also get here if id is empty. + return; + } + + // OK. Now we can search the keybag + KMX_DWORD keyIndex = 0; + auto *key2 = kmxplus->key2Helper.findKeyByStringId(strId, keyIndex); + assert(key2 != nullptr); + if (key2 == nullptr) { + return; + } + + // Now, look for the _first_ candidate vkey match in the kmap. + for (KMX_DWORD kmapIndex = 0; kmapIndex < kmxplus->key2->kmapCount; kmapIndex++) { + auto *kmap = kmxplus->key2Helper.getKmap(kmapIndex); + assert(kmap != nullptr); + if (kmap->key == keyIndex) { + k = {(km_kbp_virtual_key)kmap->vkey, (uint16_t)kmap->mod}; + return; + } + } + // Else, unfound + return; +} + + +void +LdmlJsonTestSource::next_action(ldml_action &fillin) { + if ((action_index+1) >= data["/actions"_json_pointer].size()) { + // at end, done + fillin.type = LDML_ACTION_DONE; + return; + } + + action_index++; + auto action = data["/actions"_json_pointer].at(action_index); + + // is it a check event? + auto as_check = action["/check/result"_json_pointer]; + if (as_check.is_string()) { + fillin.type = LDML_ACTION_CHECK_EXPECTED; + fillin.string = LdmlTestSource::parse_u8_source_string(as_check.get()); + return; + } + + // is it a keystroke by id? + auto as_key = action["/keystroke/key"_json_pointer]; + if (as_key.is_string()) { + fillin.type = LDML_ACTION_KEY_EVENT; + auto keyId = LdmlTestSource::parse_u8_source_string(as_key.get()); + // now, look up the key + set_key_from_id(fillin.k, keyId); + return; + } + // TODO-LDML: handle gesture, etc + + auto as_emit = action["/emit/to"_json_pointer]; + if (as_emit.is_string()) { + fillin.type = LDML_ACTION_EMIT_STRING; + fillin.string = LdmlTestSource::parse_u8_source_string(as_emit.get()); + return; + } + + // TODO-LDML: error passthrough + std::cerr << "TODO-LDML: Error, unknown/unhandled action: " << action << std::endl; + fillin.type = LDML_ACTION_DONE; +} + +const std::u16string & +LdmlJsonTestSource::get_context() const { + return context; +} + +int LdmlJsonTestSource::load(const nlohmann::json &data) { + this->data = data; // TODO-LDML + auto startContext = data["/startContext/to"_json_pointer]; + context = LdmlTestSource::parse_u8_source_string(startContext); + + return 0; +} + +LdmlJsonTestSourceFactory::LdmlJsonTestSourceFactory() : test_map() { +} + +km::kbp::path +LdmlJsonTestSourceFactory::kmx_to_test_json(const km::kbp::path &kmx) { + km::kbp::path p = kmx; + p.replace_extension(TEST_JSON_SUFFIX); + return p; +} + +int LdmlJsonTestSourceFactory::load(const km::kbp::path &compiled, const km::kbp::path &path) { + std::ifstream json_file(path.native()); + if (!json_file) { + return -1; // no file + } + nlohmann::json data = nlohmann::json::parse(json_file); + if (data.empty()) { + return __LINE__; // empty + } + + // check and load the KMX (yes, once again) + if(!km::kbp::ldml_processor::is_kmxplus_file(compiled, rawdata)) { + std::cerr << "Reading KMX for test purposes failed: " << compiled << std::endl; + return __LINE__; + } + + auto comp_keyboard = (const km::kbp::kmx::COMP_KEYBOARD*)rawdata.data(); + // initialize the kmxplus object with our copy + kmxplus.reset(new km::kbp::kmx::kmx_plus(comp_keyboard, rawdata.size())); + + if (!kmxplus->is_valid()) { + std::cerr << "kmx_plus invalid" << std::endl; + return __LINE__; + } + + if (!kmxplus->key2Helper.valid()) { + std::cerr << "kmx_plus invalid" << std::endl; + return __LINE__; + } + + auto conformsTo = data["/keyboardTest/conformsTo"_json_pointer].get(); + assert_or_return(std::string(LDML_CLDR_VERSION_LATEST) == conformsTo); + auto info_keyboard = data["/keyboardTest/info/keyboard"_json_pointer].get(); + auto info_author = data["/keyboardTest/info/author"_json_pointer].get(); + auto info_name = data["/keyboardTest/info/name"_json_pointer].get(); + // TODO-LDML: store these elsewhere? + std::cout << "JSON: reading " << info_name << " test of " << info_keyboard << " by " << info_author << std::endl; + + // TODO-LDML: repertoire test + + auto all_tests = data["/keyboardTest/tests"_json_pointer]; + assert_or_return((!all_tests.empty()) && (all_tests.size() > 0)); + + for(auto tests : all_tests) { + auto tests_name = tests["/name"_json_pointer].get(); + for (auto test : tests["/test"_json_pointer]) { + auto test_name = test["/name"_json_pointer].get(); + std::string test_path; + test_path.append(tests_name).append("/").append(test_name); + std::cout << "JSON: reading " << info_name << "/" << test_path << std::endl; + + std::unique_ptr subtest(new LdmlJsonTestSource(test_path, kmxplus.get())); + assert_or_return(subtest->load(test) == 0); + test_map[test_path] = std::unique_ptr(subtest.release()); + } + } + + return 0; +} + +const JsonTestMap& +LdmlJsonTestSourceFactory::get_tests() const { + return test_map; +} + + +} // namespace tests +} // namespace km diff --git a/core/tests/unit/ldml/ldml_test_source.hpp b/core/tests/unit/ldml/ldml_test_source.hpp new file mode 100644 index 00000000000..f4bf716c34c --- /dev/null +++ b/core/tests/unit/ldml/ldml_test_source.hpp @@ -0,0 +1,126 @@ +#ifndef __KMX_TEST_SOURCE_HPP__ +#define __KMX_TEST_SOURCE_HPP__ + +#include "path.hpp" + +#include +#include + +#include "kmx/kmx_plus.h" + +namespace km { +namespace tests { + +struct key_event { + km_kbp_virtual_key vk; + uint16_t modifier_state; +}; + +enum ldml_action_type { + /** + * Done. no more actions + */ + LDML_ACTION_DONE, + /** + * key_event - a vkey + */ + LDML_ACTION_KEY_EVENT, + /** + * string - emit this + */ + LDML_ACTION_EMIT_STRING, + /** + * expected text + */ + LDML_ACTION_CHECK_EXPECTED, + // TODO-LDML: gestures, etc? +}; + +struct ldml_action { + ldml_action_type type; + key_event k; + std::u16string string; +}; + +/** + * pure virtual representing a test source, or a specific subtest + */ +class LdmlTestSource { +public: + LdmlTestSource(); + virtual ~LdmlTestSource(); + virtual void next_action(ldml_action &fillin) = 0; + virtual int caps_lock_state(); + virtual void toggle_caps_lock_state(); + virtual void set_caps_lock_on(bool caps_lock_on); + virtual km_kbp_status get_expected_load_status(); + virtual const std::u16string &get_context() const = 0; + virtual bool get_expected_beep() const; + + // helper functions + static key_event char_to_event(char ch); + static uint16_t get_modifier(std::string const m); + static km_kbp_virtual_key get_vk(std::string const &vk); + static std::u16string parse_source_string(std::string const &s); + static std::u16string parse_u8_source_string(std::string const &s); + +private: + bool _caps_lock_on = false; +}; + +typedef std::map> JsonTestMap; + +class LdmlJsonTestSourceFactory { + public: + LdmlJsonTestSourceFactory(); + /** + * @param compiled the KMX - for lookup + * @param path the json + */ + int load(const km::kbp::path &compiled, const km::kbp::path &path); + + static km::kbp::path kmx_to_test_json(const km::kbp::path& kmx); + + const JsonTestMap& get_tests() const; + private: + JsonTestMap test_map; + // copy of the kbd data, for lookups + std::vector rawdata; + std::unique_ptr kmxplus; +}; + + +class LdmlEmbeddedTestSource : public LdmlTestSource { +public: + LdmlEmbeddedTestSource(); + virtual ~LdmlEmbeddedTestSource(); + + /** + * Load the test_source from comments in the .xml source + */ + int load_source(const km::kbp::path &path); + + virtual km_kbp_status get_expected_load_status(); + virtual const std::u16string &get_context() const; + virtual bool get_expected_beep() const; + + virtual void next_action(ldml_action &fillin); + +private: + + bool is_token(const std::string token, std::string &line); + key_event vkey_to_event(std::string const &vk_event); + key_event next_key(std::string &keys); + key_event next_key(); + + std::string keys = ""; + std::u16string expected = u"", context = u""; + bool expected_beep = false; + bool expected_error = false; + bool is_done = false; +}; + +} // namespace tests +} // namespace km + +#endif // __LDML_TEST_SOURCE_HPP__ diff --git a/core/tests/unit/ldml/meson.build b/core/tests/unit/ldml/meson.build new file mode 100644 index 00000000000..7dc3d81805c --- /dev/null +++ b/core/tests/unit/ldml/meson.build @@ -0,0 +1,83 @@ +# Copyright: © SIL International. +# Description: Cross platform build script to compile kmkbpldml API unit tests. +# Create Date: 5 Aug 2022 +# Authors: Marc Durdin +# + +if compiler.get_id() == 'gcc' or compiler.get_id() == 'clang' or compiler.get_id() == 'emscripten' + warns = [ + '-Wno-missing-field-initializers', + '-Wno-unused-parameter' + ] +else + warns = [] +endif + +# Build all keyboards in output folder; these are defined here and used in the +# keyboards subdir + +tests = [] +invalid_tests = [] + +# Setup copying of source files, used in child subdir calls + +if build_machine.system() == 'windows' + copy_cmd = [find_program('cmd.exe', required: true), '/c', 'copy'] +else + copy_cmd = [find_program('cp', required: true)] +endif + +if node.found() + # Note: if node is not available, we cannot build the keyboards; build.sh + # emits a warning that the ldml keyboard tests will be skipped + subdir('keyboards') + subdir('invalid-keyboards') +endif + + +# Build ldml test executable + +if compiler.get_id() == 'emscripten' + tests_flags = ['--embed-file', join_paths(meson.current_build_dir(),'keyboards','@')] + tests_flags += ['--embed-file', join_paths(meson.current_build_dir(),'invalid-keyboards','@')] + test_path = '/' + invalid_test_path = '/' +else + tests_flags = [] + test_path = join_paths(meson.current_build_dir(),'keyboards') + invalid_test_path = join_paths(meson.current_build_dir(),'invalid-keyboards') +endif + +ldml = executable('ldml', + 'ldml.cpp', + 'ldml_test_source.cpp', + cpp_args: defns + warns, + include_directories: [inc, libsrc, '../../../../developer/src/ext/json'], + link_args: links + tests_flags, + objects: lib.extract_all_objects(recursive: false)) + +# Run tests on all keyboards (`tests` defined in keyboards/meson.build) + +foreach kbd : tests + kbd_src = join_paths(test_path, kbd) + '.xml' + kbd_obj = join_paths(test_path, kbd) + '.kmx' + test(kbd, ldml, args: [kbd_src, kbd_obj], suite: 'ldml-keyboards') +endforeach + +# Run tests on all invalid keyboards (`invalid_tests` defined in invalid-keyboards/meson.build) + +foreach kbd : invalid_tests + kbd_src = join_paths(invalid_test_path, kbd) + '.xml' + kbd_obj = join_paths(invalid_test_path, kbd) + '.kmx' + test(kbd, ldml, args: [kbd_src, kbd_obj], suite: 'ldml-invalid-keyboards') + # todo: consider if we should use `should_fail: true`? +endforeach + +# Build and run additional test_kmx_plus test + +e = executable('test_kmx_plus', 'test_kmx_plus.cpp', + cpp_args: defns + warns, + include_directories: [inc, libsrc], + link_args: links + tests_flags, + objects: lib.extract_all_objects(recursive: false)) +test('test_kmx_plus', e, suite: 'ldml') diff --git a/core/tests/unit/ldml/test_kmx_plus.cpp b/core/tests/unit/ldml/test_kmx_plus.cpp new file mode 100644 index 00000000000..d5a83f1317d --- /dev/null +++ b/core/tests/unit/ldml/test_kmx_plus.cpp @@ -0,0 +1,52 @@ +#include +#include "kmx/kmx_plus.h" +#include "../test_assert.h" + +using namespace km::kbp::kmx; + +int main(int argc, const char *argv[]) { + COMP_KMXPLUS_KEYS_KEY e[2] = { + { + 0x00000127, // to = U+0127 = 295 + 0x00000000 // flags: !EXTEND + }, + { + 0x0001F640, // to + 0x00000000 // flags: !EXTEND + } + }; + COMP_KMXPLUS_ELEM_ELEMENT elems[2] = { + { + 0x00000127, // to = U+0127 = 295 + 0x00000000 // flags: !LDML_ELEM_FLAGS_UNICODE_SET + }, + { + 0x0001F640, // to + 0x00000000 // flags: !LDML_ELEM_FLAGS_UNICODE_SET + } + }; + std::u16string s0 = e[0].get_string(); + assert_equal(s0.length(), 1); + assert_equal(s0.at(0), 0x0127); + assert(s0 == std::u16string(u"ħ")); + + std::u16string s1 = e[1].get_string(); + assert_equal(s1.length(), 2); + assert_equal(s1.at(0), 0xD83D); + assert_equal(s1.at(1), 0xDE40); + assert(s1 == std::u16string(u"🙀")); + + // now, elems. Parallel. + std::u16string es0 = elems[0].get_string(); + assert_equal(es0.length(), 1); + assert_equal(es0.at(0), 0x0127); + assert(es0 == std::u16string(u"ħ")); + + std::u16string es1 = elems[1].get_string(); + assert_equal(es1.length(), 2); + assert_equal(es1.at(0), 0xD83D); + assert_equal(es1.at(1), 0xDE40); + assert(es1 == std::u16string(u"🙀")); + + return 0; +} diff --git a/core/tests/unit/meson.build b/core/tests/unit/meson.build index a0125baed36..2572dd57518 100644 --- a/core/tests/unit/meson.build +++ b/core/tests/unit/meson.build @@ -1,4 +1,10 @@ +node = find_program('node', required: true) + +hextobin_root = join_paths(meson.source_root(),'..','common','tools','hextobin','build','hextobin.js') +hextobin_cmd = [node, hextobin_root] + subdir('json') subdir('utftest') subdir('kmnkbd') subdir('kmx') +subdir('ldml') diff --git a/core/tools/ldml-const-builder/build.sh b/core/tools/ldml-const-builder/build.sh new file mode 100755 index 00000000000..3b1924e7ee9 --- /dev/null +++ b/core/tools/ldml-const-builder/build.sh @@ -0,0 +1,50 @@ +#!/usr/bin/env bash +# +# Builds /core/include/ldml/keyboardprocessor_ldml.h from /core/include/ldml/keyboardprocessor_ldml.ts +# + +# 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" + +# This script runs from its own folder +cd "$(dirname "$THIS_SCRIPT")" + +KBP_LDML_H_FILE="../../include/ldml/keyboardprocessor_ldml.h" + +################################ Main script ################################ + +builder_describe "Build and run the constant builder for LDML" clean build run +builder_parse "$@" + +# TODO: build if out-of-date if test is specified +# TODO: configure if npm has not been run, and build is specified + + +if builder_start_action clean; then + rm -rf ../../include/ldml/build/ + # Not removing ${KBP_LDML_H_FILE} as it is checked in + builder_finish_action success clean +fi + +if builder_start_action build; then + # Generate index.ts + npx tsc -b ../../include/ldml/tsconfig.build.json + + builder_finish_action success build +fi + +if builder_start_action run; then + node --enable-source-maps ../../include/ldml/ldml-const-builder/ldml-const-builder.js > ${KBP_LDML_H_FILE} + echo "Updated ${KBP_LDML_H_FILE}" + + builder_finish_action success run +fi + diff --git a/core/wasm.build.win.in b/core/wasm.build.win.in index bc61cbbcc02..e6c20c0f723 100644 --- a/core/wasm.build.win.in +++ b/core/wasm.build.win.in @@ -1,6 +1,6 @@ [binaries] -c = ['python.exe', '$EMSCRIPTEN_BASE/emcc.py'] -cpp = ['python.exe', '$EMSCRIPTEN_BASE/em++.py'] +c = ['python.exe', '$EMSCRIPTEN_BASE/emcc.py', '-s', '-O2'] +cpp = ['python.exe', '$EMSCRIPTEN_BASE/em++.py', '-s', '-O2'] ar = ['python.exe', '$EMSCRIPTEN_BASE/emar.py'] [properties] diff --git a/developer/src/.gitignore b/developer/src/.gitignore index 1616224fd6a..5e1280d2cad 100644 --- a/developer/src/.gitignore +++ b/developer/src/.gitignore @@ -31,7 +31,7 @@ inst/setup.inf inst/setup.zip inst/templates.wxs inst/xml.wxs -inst/kmlmc.wxs +inst/kmc.wxs # /developer/TIKE/ tike/stock.kct @@ -86,7 +86,8 @@ tds_file.txt # Breakpad files generated for Sentry **/*.sym - - +# All kmc projects have build/ folders +kmc/build/ +kmc-*/build/ diff --git a/developer/src/Defines.mak b/developer/src/Defines.mak index b4b567c6e7f..0a92bee6957 100644 --- a/developer/src/Defines.mak +++ b/developer/src/Defines.mak @@ -12,8 +12,6 @@ DEVELOPER_PROGRAM=$(DEVELOPER_ROOT)\bin DEVELOPER_OUTLIB=$(DEVELOPER_ROOT)\lib DEVELOPER_DEBUGPATH=$(DEVELOPER_ROOT)\debug -KEYMAN_MODELCOMPILER_ROOT=$(DEVELOPER_ROOT)\src\kmlmc - DEVELOPER_DELPHIDPKPARAMS=-Q -B -GD -VT -^$C+ -^$D+ -^$J+ -^$L+ -^$O+ -^$Q- -^$R- -^$W+ -^$Y+ -E. $(DELPHIWARNINGS) -I$(DELPHIINCLUDES) -U$(DELPHIINCLUDES) -R$(DELPHIINCLUDES) -NSVcl;Vcl.Imaging;Vcl.Touch;Vcl.Samples;Vcl.Shell;System;Xml;Web;Soap;Winapi;System.Win -LE$(DEVELOPER_OUTLIB) -LN$(DEVELOPER_OUTLIB) -NSData -NUobj\Win32\$(TARGET_PATH) DEVELOPER_DCC32DPK=cmd /c "$(DCC32PATH)\dcc32.exe" $(DEVELOPER_DELPHIDPKPARAMS) diff --git a/developer/src/Makefile b/developer/src/Makefile index 10231b0ea22..b2059a0cd99 100644 --- a/developer/src/Makefile +++ b/developer/src/Makefile @@ -3,9 +3,9 @@ # !ifdef NODELPHI -TARGETS=build-tools kmcmpdll kmanalyze kmdecomp server kmlmc +TARGETS=build-tools kmcmpdll kmanalyze kmdecomp server kmc !else -TARGETS=build-tools kmcmpdll kmcomp kmanalyze kmconvert tike samples setup inst kmdecomp server kmlmc +TARGETS=build-tools kmcmpdll kmcomp kmanalyze kmconvert tike samples setup inst kmdecomp server kmc !endif EXCLUDEPATHDEFINES=1 @@ -65,8 +65,24 @@ server: .virtual cd $(DEVELOPER_ROOT)\src\server $(MAKE) $(TARGET) -kmlmc: .virtual - cd $(DEVELOPER_ROOT)\src\kmlmc +kmc: kmc-keyboard kmc-model kmc-model-info kmc-package + cd $(DEVELOPER_ROOT)\src\kmc + $(MAKE) $(TARGET) + +kmc-keyboard: .virtual + cd $(DEVELOPER_ROOT)\src\kmc-keyboard + $(MAKE) $(TARGET) + +kmc-model: .virtual + cd $(DEVELOPER_ROOT)\src\kmc-model + $(MAKE) $(TARGET) + +kmc-model-info: .virtual + cd $(DEVELOPER_ROOT)\src\kmc-model-info + $(MAKE) $(TARGET) + +kmc-package: .virtual + cd $(DEVELOPER_ROOT)\src\kmc-package $(MAKE) $(TARGET) # ---------------------------------------------------------------------- diff --git a/developer/src/README.md b/developer/src/README.md index 6054cd5a61d..e4de6c13284 100644 --- a/developer/src/README.md +++ b/developer/src/README.md @@ -27,10 +27,10 @@ Building and signing of the installation archive keymandeveloper.msi. ### src/inst/node -Contains a minimal distribution of node.js intended just for compiling lexical -models. If more dependencies are required, then the developer will be expected -to install node.js themselves; this gets users going without requiring a large -installer or a full node.js install. +Contains a minimal distribution of node.js intended just for running kmc. If +more dependencies are required, then the developer will be expected to install +node.js themselves; this gets users going without requiring a large installer or +a full node.js install. ## src/kmanalyze - kmanalyze.exe @@ -66,27 +66,33 @@ Unsupported utility decompiles a Keyman .kmx keyboard. It will produce a .kmn source file and optionally a .ico or .bmp image. see kmdecomp/kmdecomp.md for details. -## src/kmlmc +## src/kmc -node-based compilers for lexical models +node-based next generation compiler, hosts kmc, kmlmi, kmlmc, kmlmp -### src/kmlmc/kmlmc - Lexical Model Compiler +### src/kmc-keyboard - LDML Keyboard Compiler + +Next Generation keyboard compiler package - LDML keyboards only at present. +Command line access through kmc. + +### src/kmc-model - Lexical Model Compiler The Lexical Model Compiler, kmlmc, runs on nodeJS on all supported desktop -platforms. +platforms. Command line access through kmc/kmlmc. -### src/kmlmc/kmlmp - Package Compiler +### src/kmc-package - Package 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. +one. Command line access through kmc/kmlmp. -### src/kmlmc/kmlmi - Model Info Compiler +### src/kmc-model-info - Model Info Compiler -Merges .model_info files for use on the Keyman Cloud lexical model repository -at https://github.com/keymanapp/lexical-models. +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. ## src/samples diff --git a/developer/src/common/delphi/lexicalmodels/Keyman.System.LexicalModelUtils.pas b/developer/src/common/delphi/lexicalmodels/Keyman.System.LexicalModelUtils.pas index b7e461b0783..c367331eee7 100644 --- a/developer/src/common/delphi/lexicalmodels/Keyman.System.LexicalModelUtils.pas +++ b/developer/src/common/delphi/lexicalmodels/Keyman.System.LexicalModelUtils.pas @@ -49,7 +49,7 @@ implementation const SLexicalModelExtension = '.model.js'; - // The model ID SHOULD adhere to this pattern (see also developer/src/kmlmc/index.ts): + // The model ID SHOULD adhere to this pattern (see also developer/src/kmc-model): // author .bcp47 .uniq MODEL_ID_PATTERN_JS = '^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_-]*\.[a-z_][a-z0-9_]*\.model\.js$'; MODEL_ID_PATTERN_TS = '^[a-z_][a-z0-9_]*\.[a-z_][a-z0-9_-]*\.[a-z_][a-z0-9_]*\.model\.ts$'; @@ -70,7 +70,7 @@ class function TLexicalModelUtils.DoesTSFilenameFollowLexicalModelConventions( class function TLexicalModelUtils.DoesPackageFilenameFollowLexicalModelConventions( const Name: string): Boolean; -// The model ID SHOULD adhere to this pattern (see also developer/src/kmlmc/index.ts): +// The model ID SHOULD adhere to this pattern (see also developer/src/kmc-model): // author .bcp47 .uniq begin Result := TRegEx.IsMatch(ExtractFileName(Name), MODEL_ID_PATTERN_PACKAGE); diff --git a/developer/src/inst/Makefile b/developer/src/inst/Makefile index d6606be97b5..ae2e328c821 100644 --- a/developer/src/inst/Makefile +++ b/developer/src/inst/Makefile @@ -6,7 +6,7 @@ # ---------------------------------------------------------------------- -DEVELOPER_FILES=kmdev.wixobj xml.wixobj cef.wixobj templates.wixobj kmlmc.wixobj server.wixobj +DEVELOPER_FILES=kmdev.wixobj xml.wixobj cef.wixobj templates.wixobj server.wixobj kmc.wixobj setup: # @@ -60,7 +60,7 @@ clean: -del /Q xml.wxs -del /Q cef.wxs -del /Q templates.wxs - -del /Q kmlmc.wxs + -del /Q kmc.wxs test-releaseexists: cd $(DEVELOPER_ROOT)\src\inst diff --git a/developer/src/inst/download.in.mak b/developer/src/inst/download.in.mak index 111aca754c7..14b01c82ba4 100644 --- a/developer/src/inst/download.in.mak +++ b/developer/src/inst/download.in.mak @@ -16,7 +16,7 @@ KEYMAN_WIX_TEMP_BASE=$(TEMP)\keyman_wix_build KEYMAN_WIX_TEMP_XML=$(TEMP)\keyman_wix_build\xml KEYMAN_WIX_TEMP_CEF=$(TEMP)\keyman_wix_build\cef KEYMAN_WIX_TEMP_TEMPLATES=$(TEMP)\keyman_wix_build\templates -KEYMAN_WIX_TEMP_MODELCOMPILER=$(TEMP)\keyman_wix_build\ModelCompiler +KEYMAN_WIX_TEMP_KMC=$(TEMP)\keyman_wix_build\kmc KEYMAN_WIX_TEMP_SERVER=$(TEMP)\keyman_wix_build\Server KEYMAN_WIX_KMDEV_SERVER=$(DEVELOPER_ROOT)\bin\server @@ -30,15 +30,15 @@ copykmdev: makeinstaller make-kmcomp-install-zip 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 -candle: heat-cef heat-xml heat-templates heat-model-compiler heat-server +candle: heat-cef heat-xml heat-templates heat-server heat-kmc $(WIXCANDLE) -dVERSION=$VersionWin -dRELEASE=$VersionRelease kmdev.wxs $(WIXCANDLE) -dVERSION=$VersionWin -dRELEASE=$VersionRelease -dXmlSourceDir=$(DEVELOPER_ROOT)\src\tike\xml xml.wxs $(WIXCANDLE) -dVERSION=$VersionWin -dRELEASE=$VersionRelease -dCefSourceDir=$(KEYMAN_CEF4DELPHI_ROOT) cef.wxs $(WIXCANDLE) -dVERSION=$VersionWin -dRELEASE=$VersionRelease -dTemplatesSourceDir=$(KEYMAN_DEVELOPER_TEMPLATES_ROOT) templates.wxs - $(WIXCANDLE) -dVERSION=$VersionWin -dRELEASE=$VersionRelease -dModelCompilerSourceDir=$(KEYMAN_WIX_TEMP_MODELCOMPILER) kmlmc.wxs + $(WIXCANDLE) -dVERSION=$VersionWin -dRELEASE=$VersionRelease -dkmcSourceDir=$(KEYMAN_WIX_TEMP_KMC) kmc.wxs $(WIXCANDLE) -dVERSION=$VersionWin -dRELEASE=$VersionRelease -dServerSourceDir=$(KEYMAN_WIX_KMDEV_SERVER) server.wxs -clean-heat: clean-heat-model-compiler +clean-heat: clean-heat-kmc heat-xml: # We copy the files to a temp folder in order to exclude thumbs.db, .vs, etc from harvesting @@ -79,19 +79,20 @@ heat-server: # When we candle/light build, we can grab the source files from the proper root so go ahead and delete the temp folder again -rmdir /s/q $(KEYMAN_WIX_TEMP_SERVER) -heat-model-compiler: - cd $(KEYMAN_MODELCOMPILER_ROOT) +heat-kmc: + cd $(DEVELOPER_ROOT)\src\kmc # Build the distributable package - $(GIT_BASH_FOR_KEYMAN) bundle.sh --build-path "$(KEYMAN_WIX_TEMP_BASE)" + mkdir $(KEYMAN_WIX_TEMP_KMC) + $(GIT_BASH_FOR_KEYMAN) build.sh bundle --build-path "$(KEYMAN_WIX_TEMP_KMC)" # Build the .wxs file cd $(DEVELOPER_ROOT)\src\inst - $(WIXHEAT) dir $(KEYMAN_WIX_TEMP_MODELCOMPILER) -o kmlmc.wxs -ag -cg ModelCompiler -dr INSTALLDIR -var var.ModelCompilerSourceDir -wx -nologo + $(WIXHEAT) dir $(KEYMAN_WIX_TEMP_KMC) -o kmc.wxs -ag -cg kmc -dr INSTALLDIR -var var.kmcSourceDir -wx -nologo -clean-heat-model-compiler: +clean-heat-kmc: # the production build generates files that are not in source, e.g. .ps1 scripts # When we candle/light build, we can grab the source files from the proper root so go ahead and delete the temp folder again - -rmdir /s/q $(KEYMAN_WIX_TEMP_MODELCOMPILER) + -rmdir /s/q $(KEYMAN_WIX_TEMP_KMC) makeinstaller: cd $(DEVELOPER_ROOT)\src\inst @@ -124,6 +125,9 @@ make-kmcomp-install-zip: copy-schemas projects\* \ server\* +# TODO: are these required? +# kpj.schema.json kvks.schema.json \ +# ldml-keyboard.schema.json ldml-keyboardtest.schema.json \ copy-schemas: copy $(KEYMAN_ROOT)\common\schemas\keyboard_info\keyboard_info.source.json $(DEVELOPER_ROOT)\bin diff --git a/developer/src/inst/kmdev.wxs b/developer/src/inst/kmdev.wxs index 34feaeed66a..0847b93cc92 100644 --- a/developer/src/inst/kmdev.wxs +++ b/developer/src/inst/kmdev.wxs @@ -264,6 +264,10 @@ + + + + @@ -287,7 +291,7 @@ - + diff --git a/developer/src/inst/node/kmc.cmd b/developer/src/inst/node/kmc.cmd new file mode 100644 index 00000000000..c1ac96270f7 --- /dev/null +++ b/developer/src/inst/node/kmc.cmd @@ -0,0 +1,4 @@ +@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" --enable-source-maps "%~dp0\kmc\kmc.cjs" %* diff --git a/developer/src/inst/node/kmlmc.cmd b/developer/src/inst/node/kmlmc.cmd index a2d6a012e92..81b87c0f9ef 100644 --- a/developer/src/inst/node/kmlmc.cmd +++ b/developer/src/inst/node/kmlmc.cmd @@ -1,4 +1,4 @@ @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\ModelCompiler\dist\kmlmc.js" %* +@"%~dp0\node.js\node.exe" --enable-source-maps "%~dp0\kmc\kmlmc.cjs" %* diff --git a/developer/src/inst/node/kmlmi.cmd b/developer/src/inst/node/kmlmi.cmd index fe8c4457de9..c02a72891c5 100644 --- a/developer/src/inst/node/kmlmi.cmd +++ b/developer/src/inst/node/kmlmi.cmd @@ -1,4 +1,4 @@ @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\ModelCompiler\dist\kmlmi.js" %* +@"%~dp0\node.js\node.exe" --enable-source-maps "%~dp0\kmc\kmlmi.cjs" %* diff --git a/developer/src/inst/node/kmlmp.cmd b/developer/src/inst/node/kmlmp.cmd index 7eeb0c25860..8994cf4db35 100644 --- a/developer/src/inst/node/kmlmp.cmd +++ b/developer/src/inst/node/kmlmp.cmd @@ -1,4 +1,4 @@ @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\ModelCompiler\dist\kmlmp.js" %* +@"%~dp0\node.js\node.exe" --enable-source-maps "%~dp0\kmc\kmlmp.cjs" %* diff --git a/developer/src/kmanalyze/kmanalyze.cpp b/developer/src/kmanalyze/kmanalyze.cpp index 6feebfb5c49..579f85e0de4 100644 --- a/developer/src/kmanalyze/kmanalyze.cpp +++ b/developer/src/kmanalyze/kmanalyze.cpp @@ -2,7 +2,6 @@ // #include "pch.h" -#include "../../../common/windows/cpp/include/crc32.h" #include "../../../common/windows/cpp/include/keymanversion.h" #include #include @@ -11,7 +10,6 @@ BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard); -BOOL VerifyChecksum(LPBYTE buf, LPDWORD CheckSum, DWORD sz); void Err(const char *p); int DoKeyboardAnalysis(LPKEYBOARD kbd, char *keyboardID, char *keyboardJSFilename, char *outputfilename); void MapVirtualKeys(void); @@ -154,26 +152,22 @@ BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard) if (ckbp->dwFileVersion < VERSION_MIN || ckbp->dwFileVersion > VERSION_MAX) { - /* Old or new version -- identify the desired program version */ - if (VerifyChecksum(buf, &kbp->dwCheckSum, sz)) - { - kbp->dpStoreArray = (LPSTORE)(buf + ckbp->dpStoreArray); - for (sp = kbp->dpStoreArray, i = 0; i < kbp->cxStoreArray; i++, sp++) - if (sp->dwSystemID == TSS_COMPILEDVERSION) - { - char buf2[256]; - wsprintf(buf2, "Wrong File Version: file version is %ls", ((PBYTE)kbp) + (INT_PTR)sp->dpString); - delete buf; - Err(buf2); - return FALSE; - } + kbp->dpStoreArray = (LPSTORE)(buf + ckbp->dpStoreArray); + for (sp = kbp->dpStoreArray, i = 0; i < kbp->cxStoreArray; i++, sp++) { + if (sp->dwSystemID == TSS_COMPILEDVERSION) + { + char buf2[256]; + wsprintf(buf2, "Wrong File Version: file version is %ls", ((PBYTE)kbp) + (INT_PTR)sp->dpString); + delete buf; + Err(buf2); + return FALSE; + } } - delete buf; Err("Unknown File Version: try using the latest version of KMDECOMP"); + delete buf; + Err("Unknown File Version: try using the latest version of KMDECOMP"); return FALSE; } - if (!VerifyChecksum(buf, &kbp->dwCheckSum, sz)) { delete buf; Err("Bad Checksum in file"); return FALSE; } - kbp->dpStoreArray = (LPSTORE)(buf + ckbp->dpStoreArray); kbp->dpGroupArray = (LPGROUP)(buf + ckbp->dpGroupArray); @@ -207,17 +201,6 @@ BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard) return TRUE; } -BOOL VerifyChecksum(LPBYTE buf, LPDWORD CheckSum, DWORD sz) -{ - DWORD tempcs; - - tempcs = *CheckSum; - *CheckSum = 0; - - BuildCRCTable(); - return tempcs == CalculateBufferCRC(sz, buf); -} - //void DoContextAnalysis(LPKEYBOARD kbd, GROUPTREE *gpref, std::vector tree, LPKEY kp, PWCHAR pc, GROUPREFTYPE type) { //} diff --git a/developer/src/kmanalyze/kmanalyze.vcxproj b/developer/src/kmanalyze/kmanalyze.vcxproj index 4897b0e214f..20fd857a352 100644 --- a/developer/src/kmanalyze/kmanalyze.vcxproj +++ b/developer/src/kmanalyze/kmanalyze.vcxproj @@ -185,7 +185,6 @@ - NotUsing NotUsing diff --git a/developer/src/kmanalyze/kmanalyze.vcxproj.filters b/developer/src/kmanalyze/kmanalyze.vcxproj.filters index 4e289235dd9..14f27f1cfeb 100644 --- a/developer/src/kmanalyze/kmanalyze.vcxproj.filters +++ b/developer/src/kmanalyze/kmanalyze.vcxproj.filters @@ -38,9 +38,6 @@ Source Files - - Source Files - Source Files diff --git a/developer/src/kmc-keyboard/Makefile b/developer/src/kmc-keyboard/Makefile new file mode 100644 index 00000000000..3cf3ac055a9 --- /dev/null +++ b/developer/src/kmc-keyboard/Makefile @@ -0,0 +1,39 @@ +# +# Keyman Developer - kmc Keyboard Compiler Makefile +# + +!include ..\Defines.mak + +# We do configure here because parent Makefile calls this first; other +# kmc and kmc-* makefiles don't do it +build: configure .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh build + +configure: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh configure + +clean: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh clean + +test: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh test + +# build.sh bundle must be run from shell as it requires a temp folder to be +# passed in. See inst/download.in.mak for instantiation. + +publish: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh publish + +signcode: + @rem nothing to do + +wrap-symbols: + @rem nothing to do + +test-manifest: + @rem nothing to do + +install: + @rem nothing to do + +!include ..\Target.mak diff --git a/developer/src/kmc-keyboard/build.sh b/developer/src/kmc-keyboard/build.sh new file mode 100755 index 00000000000..04280fa49b9 --- /dev/null +++ b/developer/src/kmc-keyboard/build.sh @@ -0,0 +1,105 @@ +#!/usr/bin/env bash +# +# Compiles the kmc keyboard compiler. +# + +# 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 + +cd "$THIS_SCRIPT_PATH" + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +builder_describe "Build Keyman kmc Keyboard Compiler module" \ + "@/common/web/keyman-version" \ + "@/common/web/types" \ + "configure" \ + "build" \ + "clean" \ + "test" \ + "build-fixtures builds test fixtures for manual examination" \ + "publish publish to npm" \ + "--dry-run,-n don't actually publish, just dry run" +builder_describe_outputs \ + configure /node_modules \ + build build/src/main.js + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action clean; then + rm -rf ./build/ ./tsconfig.tsbuildinfo + builder_finish_action success clean +fi + +SCHEMAS_COPIED=false + +copy_schemas() { + if $SCHEMAS_COPIED; then + return 0 + fi + SCHEMAS_COPIED=true + # We need the schema file at runtime and bundled, so always copy it for all actions except `clean` + mkdir -p "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json" "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json" "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/common/schemas/kvks/kvks.schema.json" "$THIS_SCRIPT_PATH/build/src/" + cp "$KEYMAN_ROOT/common/schemas/kpj/kpj.schema.json" "$THIS_SCRIPT_PATH/build/src/" +} + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action configure; then + copy_schemas + verify_npm_setup + builder_finish_action success configure +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action build; then + copy_schemas + npm run build + builder_finish_action success build +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action build-fixtures; then + copy_schemas + + # Build basic.kmx and emit its checksum + mkdir -p ./build/test/fixtures + node ../kmc ./test/fixtures/basic.xml --no-compiler-version --debug --out-file ./build/test/fixtures/basic-xml.kmx + printf "${COLOR_GREY}Checksum for basic-xml.kmx: ${COLOR_PURPLE}%s${COLOR_RESET}\n" \ + "$(xxd -g 1 -l 12 ./build/test/fixtures/basic-xml.kmx | cut -d' ' -f 10-13)" + + # Generate a binary file from basic.txt for comparison purposes + node ../../../common/tools/hextobin/build/hextobin.js ./test/fixtures/basic.txt ./build/test/fixtures/basic-txt.kmx + + builder_finish_action success build-fixtures +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action test; then + copy_schemas + npm test + builder_finish_action success test +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action publish; then + copy_schemas + . "$KEYMAN_ROOT/resources/build/npm-publish.inc.sh" + npm_publish + builder_finish_action success publish +fi diff --git a/developer/src/kmc-keyboard/package.json b/developer/src/kmc-keyboard/package.json new file mode 100644 index 00000000000..823f1d61f82 --- /dev/null +++ b/developer/src/kmc-keyboard/package.json @@ -0,0 +1,54 @@ +{ + "name": "@keymanapp/kmc-keyboard", + "description": "Keyman Developer keyboard compiler", + "keywords": [ + "keyboard", + "keyman", + "ldml", + "unicode" + ], + "type": "module", + "exports": { + ".": "./build/src/main.js" + }, + "scripts": { + "build": "tsc -b", + "test": "cd test && tsc -b && cd .. && c8 --reporter=lcov --reporter=text mocha", + "prepublishOnly": "npm run build" + }, + "author": "Marc Durdin (https://github.com/mcdurdin)", + "license": "MIT", + "bugs": { + "url": "https://github.com/keymanapp/keyman/issues" + }, + "dependencies": { + "@keymanapp/keyman-version": "*", + "ajv": "^8.11.0", + "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", + "semver": "^7.3.7", + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "@types/semver": "^7.3.12", + "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "4.6.3" + }, + "mocha": { + "spec": "build/test/**/test-*.js", + "require": [ + "source-map-support/register" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" + } +} diff --git a/developer/src/kmc-keyboard/src/compiler/compiler-options.ts b/developer/src/kmc-keyboard/src/compiler/compiler-options.ts new file mode 100644 index 00000000000..d36322513bf --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/compiler-options.ts @@ -0,0 +1,13 @@ + + +export default interface CompilerOptions { + /** + * Add debug information to the .kmx file when compiling + */ + debug?: boolean; + + /** + * Add metadata about the compiler version to .kmx file when compiling + */ + addCompilerVersion?: boolean; +}; diff --git a/developer/src/kmc-keyboard/src/compiler/compiler.ts b/developer/src/kmc-keyboard/src/compiler/compiler.ts new file mode 100644 index 00000000000..7d6a8142b49 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/compiler.ts @@ -0,0 +1,163 @@ +import { LDMLKeyboardXMLSourceFileReader, LDMLKeyboard, KMXPlus, CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; +import CompilerOptions from './compiler-options.js'; +import { CompilerMessages } from './messages.js'; +import { BkspCompiler, FinlCompiler, TranCompiler } from './tran.js'; +import { DispCompiler } from './disp.js'; +import { KeysCompiler } from './keys.js'; +import { LayrCompiler } from './layr.js'; +import { LocaCompiler } from './loca.js'; +import { MetaCompiler } from './meta.js'; +import { NameCompiler } from './name.js'; +import { OrdrCompiler } from './ordr.js'; +import { VkeyCompiler } from './vkey.js'; + +import LDMLKeyboardXMLSourceFile = LDMLKeyboard.LDMLKeyboardXMLSourceFile; +import KMXPlusFile = KMXPlus.KMXPlusFile; + +const SECTION_COMPILERS = [ + BkspCompiler, + DispCompiler, + FinlCompiler, + KeysCompiler, + LayrCompiler, + LocaCompiler, + MetaCompiler, + NameCompiler, + OrdrCompiler, + TranCompiler, + VkeyCompiler, +]; + +export default class Compiler { + private readonly callbacks: CompilerCallbacks; + // private readonly options: CompilerOptions; // not currently used + + constructor (callbacks: CompilerCallbacks, _options?: CompilerOptions) { + /* + this.options = { + debug: false, + addCompilerVersion: true, + ...options + }; + */ + this.callbacks = callbacks; + } + + private buildSections(source: LDMLKeyboardXMLSourceFile) { + return SECTION_COMPILERS.map(c => new c(source, this.callbacks)); + } + + /** + * Loads a LDML Keyboard xml file and compiles into in-memory xml + * structures. + * @param filename input filename, will use callback to load from disk + * @returns the source file, or null if invalid + */ + public load(filename: string): LDMLKeyboardXMLSourceFile | null { + const reader = new LDMLKeyboardXMLSourceFileReader(this.callbacks); + const data = this.callbacks.loadFile(filename, filename); + if(!data) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: 'Unable to read XML file'})); + return null; + } + const source = reader.load(data); + if(!source) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: 'Unable to load XML file'})); + return null; + } + try { + if (!reader.validate(source, this.callbacks.loadLdmlKeyboardSchema())) { + return null; + } + } catch(e) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: e.toString()})); + return null; + } + + return source; + } + + /** + * Loads a LDML Keyboard test data xml file and compiles into in-memory xml + * structures. + * @param filename input filename, will use callback to load from disk + * @returns the source file, or null if invalid + */ + public loadTestData(filename: string): LDMLKeyboardTestDataXMLSourceFile | null { + const reader = new LDMLKeyboardXMLSourceFileReader(this.callbacks); + const data = this.callbacks.loadFile(filename, filename); + if(!data) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: 'Unable to read XML file'})); + return null; + } + const source = reader.loadTestData(data); + if(!source) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: 'Unable to load XML file'})); + return null; + } + // TODO-LDML: The unboxed data doesn't match the schema anymore. Skipping validation, for now. + + // try { + // if (!reader.validate(source, this.callbacks.loadLdmlKeyboardTestSchema())) { + // return null; + // } + // } catch(e) { + // this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: e.toString()})); + // return null; + // } + + return source; + } + + + /** + * Validates that the LDML keyboard source file and lints. Actually just + * compiles the keyboard and returns `true` if everything is good... + * @param source + * @returns true if the file validates + */ + public validate(source: LDMLKeyboardXMLSourceFile): boolean { + return !!this.compile(source); + } + + /** + * Transforms in-memory LDML keyboard xml file to an intermediate + * representation of a .kmx file. + * @param source in-memory representation of LDML keyboard xml file + * @returns KMXPlusFile intermediate file + */ + public compile(source: LDMLKeyboardXMLSourceFile): KMXPlusFile { + const sections = this.buildSections(source); + let passed = true; + + const kmx = new KMXPlusFile(); + + // These two sections are required by other sections + kmx.kmxplus.strs = new KMXPlus.Strs(); + kmx.kmxplus.elem = new KMXPlus.Elem(kmx.kmxplus.strs); + kmx.kmxplus.list = new KMXPlus.List(kmx.kmxplus.strs); + + for(let section of sections) { + if(!section.validate()) { + passed = false; + // We'll keep validating other sections anyway, so we get a full set of + // errors for the keyboard developer. + continue; + } + const sect = section.compile({strs: kmx.kmxplus.strs, elem: kmx.kmxplus.elem, list: kmx.kmxplus.list}); + + /* istanbul ignore if */ + if(!sect) { + // This should not happen -- validate() should have told us + // if something is going to fail to compile + this.callbacks.reportMessage(CompilerMessages.Fatal_SectionCompilerFailed({sect:section.id})); + passed = false; + continue; + } + kmx.kmxplus[section.id] = sect as any; + } + + return passed ? kmx : null; + } +} + diff --git a/developer/src/kmc-keyboard/src/compiler/disp.ts b/developer/src/kmc-keyboard/src/compiler/disp.ts new file mode 100644 index 00000000000..98223de132e --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/disp.ts @@ -0,0 +1,51 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlus } from '@keymanapp/common-types'; + +import { CompilerMessages } from "./messages.js"; +import { SectionCompiler } from "./section-compiler.js"; + +import GlobalSections = KMXPlus.GlobalSections; +import Disp = KMXPlus.Disp; +import DispItem = KMXPlus.DispItem; + +export class DispCompiler extends SectionCompiler { + + public get id() { + return constants.section.disp; + } + + public validate(): boolean { + let valid = true; + + const tos = new Set(); + + if (this.keyboard.displays?.display) { + for (const { to } of this.keyboard.displays?.display) { + if (tos.has(to)) { + this.callbacks.reportMessage(CompilerMessages.Error_DisplayIsRepeated({ to })); + return false; + } + tos.add(to); + } + } + + return valid; + } + + public compile(sections: GlobalSections): Disp { + let result = new Disp(); + + // displayOptions + result.baseCharacter = sections.strs.allocAndUnescapeString(this.keyboard.displays?.displayOptions?.baseCharacter); + + // displays + result.disps = this.keyboard.displays?.display.map(display => ({ + to: sections.strs.allocAndUnescapeString(display.to), + display: sections.strs.allocAndUnescapeString(display.display), + })) || []; + + result.disps.sort((a: DispItem, b: DispItem) => a.to.compareTo(b.to)); + + return result; + } +} diff --git a/developer/src/kmc-keyboard/src/compiler/keymanweb-compiler.ts b/developer/src/kmc-keyboard/src/compiler/keymanweb-compiler.ts new file mode 100644 index 00000000000..ded8a4f7cd6 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/keymanweb-compiler.ts @@ -0,0 +1,118 @@ +import { VisualKeyboard, LDMLKeyboard, TouchLayoutFileWriter } from "@keymanapp/common-types"; + +import * as path from 'path'; +import CompilerOptions from "./compiler-options.js"; +import { TouchLayoutCompiler } from "./touch-layout-compiler.js"; +import VisualKeyboardCompiler from "./visual-keyboard-compiler.js"; + +const MINIMUM_KMW_VERSION = '16.0'; + +export interface KeymanWebCompilerOptions extends CompilerOptions { +}; + +export class KeymanWebCompiler { + private readonly options: KeymanWebCompilerOptions; + private readonly nl: string; + private readonly tab: string; + + constructor(options?: KeymanWebCompilerOptions) { + this.options = { ...options }; + this.nl = this.options.debug ? "\n" : ''; + this.tab = this.options.debug ? " " : ''; + } + + public compileVisualKeyboard(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile) { + const nl = this.nl, tab = this.tab; + const vkc = new VisualKeyboardCompiler(); + const vk: VisualKeyboard.VisualKeyboard = vkc.compile(source); + + let result = + `{F: '${vk.header.unicodeFont.size}pt ${JSON.stringify(vk.header.unicodeFont.name)}', `+ + `K102: ${vk.header.flags & VisualKeyboard.VisualKeyboardHeaderFlags.kvkh102 ? 1 : 0}};${nl}` + // TODO-LDML: escape ' and " in font name correctly + `${tab}this.KV.KLS={${nl}` + + `${tab}${tab}TODO_LDML: ${vk.keys.length}${nl}` + + // TODO-LDML: fill in KLS + `${tab}}`; + + return result; + } + + public compileTouchLayout(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile) { + const tlcompiler = new TouchLayoutCompiler(); + const layout = tlcompiler.compileToJavascript(source); + const writer = new TouchLayoutFileWriter({formatted: this.options.debug}); + return writer.compile(layout); + } + + private cleanName(name: string): string { + let result = path.basename(name, '.xml').toLowerCase(); + if(!result.length) { + throw new Error(`Invalid file name ${name}`); + } + result = result.replaceAll(/[^a-z0-9]/g, '_'); + if(result.match(/^[0-9]/)) { + // Can't have a digit as initial + result = '_' + result; + } + return result; + } + + public compile(name: string, source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): string { + const nl = this.nl, tab = this.tab; + + const sName = 'Keyboard_'+this.cleanName(name); + const displayUnderlying = true; // TODO-LDML + const modifierBitmask = 0; // TODO-LDML: define the modifiers used by this keyboard + const vkDictionary = ''; // TODO-LDML: vk dictionary for touch keys + const hasSupplementaryPlaneChars = false; // TODO-LDML + const isRTL = false; // TODO-LDML + + let result = + `if(typeof keyman === 'undefined') {${nl}` + + `${tab}console.error('Keyboard requires KeymanWeb ${MINIMUM_KMW_VERSION} or later');${nl}` + + `} else {${nl}` + + `${tab}KeymanWeb.KR(new ${sName}());${nl}` + + `}${nl}` + + `function ${sName}() {${nl}` + + // `${tab}${this.setupDebug()}${nl}` + ? we may use this for modifierBitmask in future + // `${tab}this._v=(typeof keyman!="undefined"&&typeof keyman.version=="string")?parseInt(keyman.version,10):9;${nl}` + ? we probably don't need this, it's for back-compat + `${tab}this.KI="${sName}";${nl}` + + `${tab}this.KN=${JSON.stringify(source.keyboard.names.name[0])};${nl}` + + `${tab}this.KMINVER=${JSON.stringify(MINIMUM_KMW_VERSION)};${nl}` + + `${tab}this.KV=${this.compileVisualKeyboard(source)};${nl}` + + `${tab}this.KDU=${displayUnderlying ? '1' : '0'};${nl}` + + `${tab}this.KH="";${nl}` + // TODO-LDML: help text not supported + `${tab}this.KM=0;${nl}` + // TODO-LDML: mnemonic layout not supported for LDML keyboards + `${tab}this.KBVER=${JSON.stringify(source.keyboard.version?.number || '0.0')};${nl}` + + `${tab}this.KMBM=${modifierBitmask};${nl}`; + + if(isRTL) { + result += `${tab}this.KRTL=1;${nl}`; + } + + if(hasSupplementaryPlaneChars) { + result += `${tab}this.KS=1;${nl}`; + } + + if(vkDictionary != '') { + result += `${tab}this.KVKD=${JSON.stringify(vkDictionary)};${nl}`; + } + + let layoutFile = this.compileTouchLayout(source); + if(layoutFile != '') { + result += `${tab}this.KVKL=${layoutFile};${nl}`; + } + // TODO-LDML: KCSS not supported + + // TODO-LDML: embed binary keyboard for loading into Core + + // A LDML keyboard has a no-op for its gs() (begin Unicode) function, + // because the functionality is embedded in Keyman Core + result += `${tab}this.gs=function(t,e){${nl}`+ + `${tab}${tab}return 0;${nl}`+ // TODO-LDML: we will start by embedding call into Keyman Core here + `${tab}};${nl}`; + + result += `}${nl}`; + return result; + } +} \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/compiler/keys.ts b/developer/src/kmc-keyboard/src/compiler/keys.ts new file mode 100644 index 00000000000..48da90a04f3 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/keys.ts @@ -0,0 +1,238 @@ +import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { LDMLKeyboard, KMXPlus, Constants } from '@keymanapp/common-types'; +import { CompilerMessages } from './messages.js'; +import { SectionCompiler } from "./section-compiler.js"; + +import GlobalSections = KMXPlus.GlobalSections; +import Keys = KMXPlus.Keys; +import ListItem = KMXPlus.ListItem; +import KeysFlicks = KMXPlus.KeysFlicks; +import { allUsedKeyIdsInLayers, calculateUniqueKeys, translateLayerAttrToModifier, validModifier } from '../util/util.js'; + +export class KeysCompiler extends SectionCompiler { + + public get id() { + return constants.section.keys; + } + + public validate() { + let valid = true; + + // general key-level validation here, only of used keys + const usedKeys = allUsedKeyIdsInLayers(this.keyboard?.layers); + const uniqueKeys = calculateUniqueKeys([...this.keyboard.keys?.key]); + for (let key of uniqueKeys) { + const {id, flicks} = key; + if (!usedKeys.has(id)) { + continue; // unused key, ignore + } + // TODO-LDML: further key-level validation here + if (!flicks) { + continue; // no flicks + } + const flickEntry = this.keyboard.keys?.flicks?.find(x => x.id === flicks); + if (!flickEntry ) { + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_MissingFlicks({flicks, id})); + } + } + + // Kmap validation + const theLayers = this.keyboard.layers?.[0]; // TODO-LDML: handle >1 layers. #8160 + + if(!theLayers?.layer?.length) { + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_MustBeAtLeastOneLayerElement()); + } + + if(theLayers?.form == 'hardware') { + for(let layer of theLayers?.layer) { + valid = this.validateHardwareLayerForKmap(theLayers?.hardware, layer) && valid; // note: always validate even if previously invalid results found + } + } + + // TODO-LDML: some additional validation needed here? + return valid; + } + + public compile(sections: GlobalSections): Keys { + if (!this.keyboard?.keys?.key && !this.keyboard?.keys?.flicks) { + // short-circuit if no keys or flicks + return null; + } + + let sect = new Keys(sections.strs); + + // Load the flicks first + this.loadFlicks(sections, sect); + + // Now, load the keys + this.loadKeys(sections, sect); + + // Finally, kmap + // Use LayerMap + keys to generate compiled keys for hardware + const theLayers = this.keyboard.layers?.[0]; // TODO-LDML: handle >1 layers. #8160 + + if(theLayers?.form == 'hardware') { + for(let layer of theLayers.layer) { + this.compileHardwareLayerToKmap(sections, layer, sect, theLayers.hardware); + } + return sect; + } + // TODO-LDML: generate vkey mapping for touch-only keys + + return sect; + } + + public loadFlicks(sections: GlobalSections, sect: Keys) { + for (let lkflicks of this.keyboard.keys.flicks) { + let flicks: KeysFlicks = new KeysFlicks(sections.strs.allocString(lkflicks.id)); + + for (let lkflick of lkflicks.flick) { + let flags = 0; + // TODO-LDML: single char + const to = sections.strs.allocAndUnescapeString(lkflick.to); + flags |= constants.keys_flick_flags_extend; + let directions : ListItem = sections.list.allocListFromSpaces(sections.strs, lkflick.directions); + flicks.flicks.push({ + directions, + flags, + to, + }); + } + + sect.flicks.push(flicks); + } + } + + public loadKeys(sections: GlobalSections, sect: Keys) { + const usedKeys = allUsedKeyIdsInLayers(this.keyboard?.layers); + const uniqueKeys = calculateUniqueKeys([...this.keyboard.keys?.key]); + + for (let key of uniqueKeys) { + if (!usedKeys.has(key.id)) { + // TODO-LDML: linting for unused, non-implied and non-imported keys, + continue; // unused key, skip + } + let flags = 0; + const flicks = key.flicks; + if (!!key.gap) { + flags |= constants.keys_key_flags_gap; + } + if (key.transform === 'no') { + flags |= constants.keys_key_flags_notransform; + } + const id = sections.strs.allocString(key.id); + const longPress: ListItem = sections.list.allocListFromEscapedSpaces(sections.strs, key.longPress); + const longPressDefault = sections.strs.allocAndUnescapeString(key.longPressDefault); + const multiTap: ListItem = sections.list.allocListFromEscapedSpaces(sections.strs, key.multiTap); + const keySwitch = sections.strs.allocString(key.switch); // 'switch' is a reserved word + flags |= constants.keys_key_flags_extend; + const to = sections.strs.allocAndUnescapeString(key.to); // TODO-LDML: single char + const width = Math.ceil((key.width || 1) * 10.0); // default, width=1 + sect.keys.push({ + flags, + flicks, + id, + longPress, + longPressDefault, + multiTap, + switch: keySwitch, // 'switch' is a reserved word + to, + width, + }); + } + } + + /** + * TODO-LDML: from old 'keys' + * Validate for purpose of kmap + * @param hardware + * @param layer + * @returns + */ + private validateHardwareLayerForKmap(hardware: string, layer: LDMLKeyboard.LKLayer) { + let valid = true; + + const { modifier } = layer; + if (!validModifier(modifier)) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidModifier({ modifier, layer: layer.id })); + valid = false; + } + + const keymap = Constants.HardwareToKeymap.get(hardware); + if (!keymap) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidHardware({ hardware })); + valid = false; + return valid; // can't do anything else here + } + + const uniqueKeys = calculateUniqueKeys([...this.keyboard.keys?.key]); + if (layer.row.length > keymap.length) { + this.callbacks.reportMessage(CompilerMessages.Error_HardwareLayerHasTooManyRows()); + valid = false; + } + + for (let y = 0; y < layer.row.length && y < keymap.length; y++) { + const keys = layer.row[y].keys.split(' '); + + if (keys.length > keymap[y].length) { + this.callbacks.reportMessage(CompilerMessages.Error_RowOnHardwareLayerHasTooManyKeys({ row: y + 1, hardware })); + valid = false; + } + + let x = -1; + for (let key of keys) { + x++; + + let keydef = uniqueKeys.find(x => x.id == key); + if (!keydef) { + this.callbacks.reportMessage(CompilerMessages.Error_KeyNotFoundInKeyBag({ keyId: key, col: x + 1, row: y + 1, layer: layer.id, form: 'hardware' })); + valid = false; + continue; + } + if (!keydef.to && !keydef.gap && !keydef.switch) { + this.callbacks.reportMessage(CompilerMessages.Error_KeyMissingToGapOrSwitch({ keyId: key })); + valid = false; + continue; + } + } + } + + return valid; + } + + + private compileHardwareLayerToKmap( + sections: GlobalSections, + layer: LDMLKeyboard.LKLayer, + sect: Keys, + hardware: string, + ): Keys { + const mod = translateLayerAttrToModifier(layer); + const keymap = Constants.HardwareToKeymap.get(hardware); + + let y = -1; + for (let row of layer.row) { + y++; + + const keys = row.keys.split(' '); + let x = -1; + for (let key of keys) { + x++; + + // TODO-LDML: we already validated that the key exists, above. + // So here we only need the ID? + // let keydef = this.keyboard.keys?.key?.find(x => x.id == key); + + sect.kmap.push({ + vkey: keymap[y][x], + mod: mod, + key, // key id, to be changed into key index at finalization + }); + } + } + return sect; + } + +} diff --git a/developer/src/kmc-keyboard/src/compiler/layr.ts b/developer/src/kmc-keyboard/src/compiler/layr.ts new file mode 100644 index 00000000000..e20855ed6e1 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/layr.ts @@ -0,0 +1,93 @@ +import { constants } from '@keymanapp/ldml-keyboard-constants'; +import { KMXPlus } from '@keymanapp/common-types'; +import { CompilerMessages } from './messages.js'; +import { SectionCompiler } from "./section-compiler.js"; +import { translateLayerAttrToModifier, validModifier } from '../util/util.js'; + + +import GlobalSections = KMXPlus.GlobalSections; +import Layr = KMXPlus.Layr; +import LayrEntry = KMXPlus.LayrEntry; +import LayrList = KMXPlus.LayrList; +import LayrRow = KMXPlus.LayrRow; + +export class LayrCompiler extends SectionCompiler { + + public get id() { + return constants.section.layr; + } + + public validate() { + let valid = true; + if (!this.keyboard.layers?.[0]?.layer?.length) { + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_MustBeAtLeastOneLayerElement()); + } + let hardwareLayers = 0; + this.keyboard.layers.forEach((layers) => { + const { hardware, form } = layers; + // TODO-LDML: in the future >1 hardware layer may be allowed, check for duplicates + if (form === 'touch') { + if (hardware) { + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_NoHardwareOnTouch({hardware})); + } + } else if (form === 'hardware') { + hardwareLayers++; + if (!hardware) { + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_MissingHardware()); + } else if (!constants.layr_list_hardware_map.get(hardware)) { + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_InvalidHardware({hardware})); + } else if (hardwareLayers > 1) { // TODO-LDML: revisit if spec changes + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_MustHaveAtMostOneLayersElementPerForm({ form })); + } + } else { + /* c8 ignore next 7 */ + // Should not be reached due to XML validation. + valid = false; + this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({ + errorText: `INTERNAL ERROR: Invalid XML: Invalid form="${form}" on layers element` + })); + } + layers.layer.forEach((layer) => { + const { modifier, id } = layer; + if (!validModifier(modifier)) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidModifier({ modifier, layer: id })); + valid = false; + } + }); + }); + return valid; + } + + public compile(sections: GlobalSections): Layr { + const sect = new Layr(); + + sect.lists = this.keyboard.layers.map((layers) => { + const hardware = constants.layr_list_hardware_map.get(layers.hardware || 'touch'); + // Don't need to check 'form' because it is checked in validate + const list: LayrList = { + hardware, + minDeviceWidth: layers.minDeviceWidth || 0, + layers: layers.layer.map((layer) => { + const entry: LayrEntry = { + id: sections.strs.allocString(layer.id), + mod: translateLayerAttrToModifier(layer), + rows: layer.row.map((row) => { + const erow: LayrRow = { + keys: row.keys.split(' ').map((id) => sections.strs.allocString(id)), + }; + return erow; + }), + }; + return entry; + }), + }; + return list; + }); + return sect; + } +} diff --git a/developer/src/kmc-keyboard/src/compiler/loca.ts b/developer/src/kmc-keyboard/src/compiler/loca.ts new file mode 100644 index 00000000000..da7d2fb8d2e --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/loca.ts @@ -0,0 +1,69 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { LDMLKeyboard, KMXPlus } from '@keymanapp/common-types'; +import { SectionCompiler } from "./section-compiler.js"; +import { CompilerMessages } from "./messages.js"; + +import GlobalSections = KMXPlus.GlobalSections; +import Loca = KMXPlus.Loca; +import LKKeyboard = LDMLKeyboard.LKKeyboard; + +export class LocaCompiler extends SectionCompiler { + + public get id() { + return constants.section.loca; + } + + /** + * + * @param keyboard + * @returns list of BCP 47 tags in the keyboard xml, potentially with invalid or repeated entries + */ + private getLocales = + (keyboard: LKKeyboard) => + [keyboard.locale].concat(Array.isArray(keyboard.locales?.locale) ? keyboard.locales.locale.map(v => v.id) : []) + + public validate(): boolean { + let valid = true; + const locales = this.getLocales(this.keyboard); + for(let tag of locales) { + try { + new Intl.Locale(tag); + } catch(e) { + if(e instanceof RangeError) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidLocale({tag})); + valid = false; + } else { + throw e; + } + } + } + return valid; + } + + public compile(sections: GlobalSections): Loca { + let result = new Loca(); + + // This also minimizes locales according to Remove Likely Subtags algorithm: + // https://www.unicode.org/reports/tr35/#Likely_Subtags + const sourceLocales = this.getLocales(this.keyboard); + const locales = sourceLocales.map((sourceLocale: string) => { + const locale = new Intl.Locale(sourceLocale).minimize().toString(); + if(locale != sourceLocale) { + this.callbacks.reportMessage(CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale, locale})); + } + return locale; + }); + + // TODO: remove `as any` cast: (Intl as any): ts lib version we have doesn't + // yet include `getCanonicalLocales` but node 16 does include it so we can + // safely use it. Also well supported in modern browsers. + const canonicalLocales = (Intl as any).getCanonicalLocales(locales) as string[]; + result.locales = canonicalLocales.map(locale => sections.strs.allocString(locale)); + + if(result.locales.length < locales.length) { + this.callbacks.reportMessage(CompilerMessages.Hint_OneOrMoreRepeatedLocales()); + } + + return result; + } +} diff --git a/developer/src/kmc-keyboard/src/compiler/messages.ts b/developer/src/kmc-keyboard/src/compiler/messages.ts new file mode 100644 index 00000000000..ceacd4be252 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/messages.ts @@ -0,0 +1,111 @@ +import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from "@keymanapp/common-types"; + +const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.KeyboardCompiler; +const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.KeyboardCompiler; +// const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.KeyboardCompiler; +const SevError = CompilerErrorSeverity.Error | CompilerErrorNamespace.KeyboardCompiler; +const SevFatal = CompilerErrorSeverity.Fatal | CompilerErrorNamespace.KeyboardCompiler; + +export class CompilerMessages { + static Error_InvalidNormalization = (o:{form: string}) => m(this.ERROR_InvalidNormalization, `Invalid normalization form '${o.form}`); + static ERROR_InvalidNormalization = SevError | 0x0001; + + static Error_InvalidLocale = (o:{tag: string}) => m(this.ERROR_InvalidLocale, `Invalid BCP 47 locale form '${o.tag}'`); + static ERROR_InvalidLocale = SevError | 0x0002; + + static Error_HardwareLayerHasTooManyRows = () => m(this.ERROR_HardwareLayerHasTooManyRows, `'hardware' layer has too many rows`); + static ERROR_HardwareLayerHasTooManyRows = SevError | 0x0003; + + static Error_RowOnHardwareLayerHasTooManyKeys = (o:{row: number, hardware: string}) => m(this.ERROR_RowOnHardwareLayerHasTooManyKeys, `Row #${o.row} on 'hardware' ${o.hardware} layer has too many keys`); + static ERROR_RowOnHardwareLayerHasTooManyKeys = SevError | 0x0004; + + static Error_KeyNotFoundInKeyBag = (o:{keyId: string, col: number, row: number, layer: string, form: string}) => + m(this.ERROR_KeyNotFoundInKeyBag, `Key '${o.keyId}' in position #${o.col} on row #${o.row} of layer ${o.layer}, form '${o.form}' not found in key bag`); + static ERROR_KeyNotFoundInKeyBag = SevError | 0x0005; + + static Hint_OneOrMoreRepeatedLocales = () => + m(this.HINT_OneOrMoreRepeatedLocales, `After minimization, one or more locales is repeated and has been removed`); + static HINT_OneOrMoreRepeatedLocales = SevHint | 0x0006; + + static Error_InvalidFile = (o:{errorText: string}) => + m(this.ERROR_InvalidFile, `The source file has an invalid structure: ${o.errorText}`); + static ERROR_InvalidFile = SevError | 0x0007; + + static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}) => + m(this.HINT_LocaleIsNotMinimalAndClean, `Locale '${o.sourceLocale}' is not minimal or correctly formatted and should be '${o.locale}'`); + static HINT_LocaleIsNotMinimalAndClean = SevHint | 0x0008; + + static Error_VkeyIsNotValid = (o:{vkey: string}) => + m(this.ERROR_VkeyIsNotValid, `Virtual key '${o.vkey}' is not found in the CLDR VKey Enum table.`); + static ERROR_VkeyIsNotValid = SevError | 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; + + static Error_InvalidVersion = (o:{version: string}) => + m(this.ERROR_InvalidVersion, `Version number '${o.version}' must be a semantic version format string.`); + static ERROR_InvalidVersion = SevError | 0x000D; + + static Error_MustBeAtLeastOneLayerElement = () => + m(this.ERROR_MustBeAtLeastOneLayerElement, `The source file must contain at least one layer element.`); + static ERROR_MustBeAtLeastOneLayerElement = SevError | 0x000E; + + static Fatal_SectionCompilerFailed = (o:{sect: string}) => + m(this.FATAL_SectionCompilerFailed, `The compiler for '${o.sect}' failed unexpectedly.`); + static FATAL_SectionCompilerFailed = SevFatal | 0x000F; + + static Error_DisplayIsRepeated = (o:{to: string}) => + m(this.ERROR_DisplayIsRepeated, `display to='${o.to}' has more than one display entry.`); + static ERROR_DisplayIsRepeated = SevError | 0x0010; + + static Error_KeyMissingToGapOrSwitch = (o:{keyId: string}) => + m(this.ERROR_KeyMissingToGapOrSwitch, `key id='${o.keyId}' must have either to=, gap=, or switch=.`); + static ERROR_KeyMissingToGapOrSwitch = SevError | 0x0011; + + static Error_MustHaveAtMostOneLayersElementPerForm = (o:{form: string}) => m(this.ERROR_MustHaveAtMostOneLayersElementPerForm, + `Must have at most one layers element with form=${o.form}`); + static ERROR_MustHaveAtMostOneLayersElementPerForm = SevError | 0x0012; + + static Error_NoHardwareOnTouch = (o:{hardware: string}) => m(this.ERROR_NoHardwareOnTouch, + `Not allowed: form=touch with hardware=${o.hardware}`); + static ERROR_NoHardwareOnTouch = SevError | 0x0013; + + static Error_MissingHardware = () => m(this.ERROR_MissingHardware, + `layers form=hardware missing hardware= attribute`); + static ERROR_MissingHardware = SevError | 0x0014; + + static Error_InvalidHardware = (o:{hardware: string}) => m(this.ERROR_InvalidHardware, + `layers has invalid value hardware=${o.hardware}`); + static ERROR_InvalidHardware = SevError | 0x0015; + + static Error_InvalidModifier = (o:{layer: string, modifier: string}) => m(this.ERROR_InvalidModifier, + `layer has invalid modifier='${o.modifier}' on layer id=${o.layer}`); + static ERROR_InvalidModifier = SevError | 0x0016; + + static Error_MissingFlicks = (o:{flicks: string, id: string}) => m(this.ERROR_MissingFlicks, + `key id=${o.id} refers to missing flicks=${o.flicks}`); + static ERROR_MissingFlicks = SevError | 0x0017; + + static severityName(code: number): string { + let severity = code & CompilerErrorSeverity.Severity_Mask; + switch(severity) { + case CompilerErrorSeverity.Info: return 'INFO'; + case CompilerErrorSeverity.Hint: return 'HINT'; + case CompilerErrorSeverity.Warn: return 'WARN'; + case CompilerErrorSeverity.Error: return 'ERROR'; + case CompilerErrorSeverity.Fatal: return 'FATAL'; + /* istanbul ignore next */ + default: return 'UNKNOWN'; + } + } +} + diff --git a/developer/src/kmc-keyboard/src/compiler/meta.ts b/developer/src/kmc-keyboard/src/compiler/meta.ts new file mode 100644 index 00000000000..feb4d79f173 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/meta.ts @@ -0,0 +1,68 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlus } from '@keymanapp/common-types'; + +import { isValidEnumValue } from "../util/util.js"; +import { CompilerMessages } from "./messages.js"; +import { SectionCompiler } from "./section-compiler.js"; +import semver from "semver"; + +import GlobalSections = KMXPlus.GlobalSections; +import Meta = KMXPlus.Meta; +import Meta_NormalizationForm = KMXPlus.Meta_NormalizationForm; +import KeyboardSettings = KMXPlus.KeyboardSettings; + +export class MetaCompiler extends SectionCompiler { + + public get id() { + return constants.section.meta; + } + + public validate(): boolean { + let valid = true; + + valid &&= this.validateNormalization(this.keyboard.info?.normalization); + valid &&= this.validateVersion(this.keyboard.version?.number); + + return valid; + } + + private validateVersion(versionNumber?: string) { + if(versionNumber !== undefined) { + if(versionNumber.match(/^[=v]/i)) { + // semver ignores a preceding '=' or 'v' + this.callbacks.reportMessage(CompilerMessages.Error_InvalidVersion({ version: versionNumber })); + return false; + } + if(!semver.parse(versionNumber, {loose: false})) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidVersion({ version: versionNumber })); + return false; + } + } + return true; + } + + private validateNormalization(normalization?: string) { + if (normalization !== undefined) { + if (!isValidEnumValue(Meta_NormalizationForm, normalization)) { + this.callbacks.reportMessage(CompilerMessages.Error_InvalidNormalization({ form: normalization })); + return false; + } + } + return true; + } + + public compile(sections: GlobalSections): Meta { + let result = new Meta(); + result.author = sections.strs.allocString(this.keyboard.info?.author); + result.conform = sections.strs.allocString(this.keyboard.conformsTo); + result.layout = sections.strs.allocString(this.keyboard.info?.layout); + result.normalization = sections.strs.allocString(this.keyboard.info?.normalization); + result.indicator = sections.strs.allocString(this.keyboard.info?.indicator); + result.version = sections.strs.allocString(this.keyboard.version?.number ?? "0.0.0"); + result.settings = + (this.keyboard.settings?.fallback == "omit" ? KeyboardSettings.fallback : 0) | + (this.keyboard.settings?.transformFailure == "omit" ? KeyboardSettings.transformFailure : 0) | + (this.keyboard.settings?.transformPartial == "hide" ? KeyboardSettings.transformPartial : 0); + return result; + } +} \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/compiler/metadata-compiler.ts b/developer/src/kmc-keyboard/src/compiler/metadata-compiler.ts new file mode 100644 index 00000000000..6ed237a87df --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/metadata-compiler.ts @@ -0,0 +1,57 @@ +import { KMX, KMXPlus } from '@keymanapp/common-types'; +import CompilerOptions from "./compiler-options.js"; +import KEYMAN_VERSION from "@keymanapp/keyman-version/keyman-version.mjs"; + +import KMXPlusData = KMXPlus.KMXPlusData; +import KMXFile = KMX.KMXFile; +import KEYBOARD = KMX.KEYBOARD; + +export default class KMXPlusMetadataCompiler { + /** + * Look for metadata fields in the KMXPlus data and copy them + * through to the relevant KMX stores + * @param kmxplus const KMXPlusData + */ + public static addKmxMetadata(kmxplus: KMXPlusData, keyboard: KEYBOARD, options: CompilerOptions): void { + // Order of stores is not significant by kmx spec, but kmxplus compiler will + // always store according to dwSystemID binary order for non-zero + // dwSystemID, then by dpName binary order, and finally by dpString binary + // order + + // TSS_NAME = 7 + // TSS_COMPILEDVERSION = 20 + // TSS_KEYBOARDVERSION = 36 + // TSS_TARGETS = 38 + + // TSS_NAME: User friendly name of keyboard + keyboard.stores.push({ + dpName: '&NAME', + dpString: kmxplus.name?.names?.[0]?.value ?? 'unknown', // Empty name should not happen, so ok to use 'unknown' here + dwSystemID: KMXFile.TSS_NAME + }); + + if(options.addCompilerVersion) { + // TSS_COMPILEDVERSION: version of the compiler + keyboard.stores.push({ + dpName: '', + dpString: KEYMAN_VERSION.VERSION_WITH_TAG, + dwSystemID: KMXFile.TSS_COMPILEDVERSION + }); + } + + // TSS_KEYBOARDVERSION: Version of the keyboard, should be semver + keyboard.stores.push({ + dpName: '&KEYBOARDVERSION', + dpString: kmxplus.meta?.version?.value ?? '1.0', // 1.0 is inferred version + dwSystemID: KMXFile.TSS_KEYBOARDVERSION + }); + + // TSS_TARGETS: which platforms are supported + keyboard.stores.push({ + dpName: '&TARGETS', + dpString: 'desktop', // TODO-LDML: support touch layouts in #7238 + dwSystemID: KMXFile.TSS_TARGETS + }); + } + +} \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/compiler/name.ts b/developer/src/kmc-keyboard/src/compiler/name.ts new file mode 100644 index 00000000000..33fad1ef9aa --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/name.ts @@ -0,0 +1,25 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlus } from '@keymanapp/common-types'; +import { SectionCompiler } from "./section-compiler.js"; + +import GlobalSections = KMXPlus.GlobalSections; +import Name = KMXPlus.Name; + +export class NameCompiler extends SectionCompiler { + + public get id() { + return constants.section.name; + } + + public validate(): boolean { + let valid = true; + valid = (this.keyboard.names?.name?.length ?? 0) > 0; + return valid; + } + + public compile(sections: GlobalSections): Name { + let result = new Name(); + result.names = this.keyboard.names?.name?.map(v => sections.strs.allocString(v.value)) ?? []; + return result; + } +} diff --git a/developer/src/kmc-keyboard/src/compiler/ordr.ts b/developer/src/kmc-keyboard/src/compiler/ordr.ts new file mode 100644 index 00000000000..89785f107d6 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/ordr.ts @@ -0,0 +1,46 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlus, LDMLKeyboard } from '@keymanapp/common-types'; +import { SectionCompiler } from "./section-compiler.js"; + +import GlobalSections = KMXPlus.GlobalSections; +import Ordr = KMXPlus.Ordr; +import OrdrItem = KMXPlus.OrdrItem; +import LKReorder = LDMLKeyboard.LKReorder; +import LKReorders = LDMLKeyboard.LKReorders; + +export class OrdrCompiler extends SectionCompiler { + + public get id() { + return constants.section.ordr; + } + + public validate(): boolean { + let valid = true; + // TODO-LDML: linting here should check for identical before+from, but this involves a double-parse which is ugly + // TODO-LDML: unicodesets means that either we fully parse them and verify conflicting rules or the linting is imperfect + return valid; + } + + private compileReorder(sections: GlobalSections, reorder: LKReorder): OrdrItem { + let result = new OrdrItem(); + result.elements = sections.elem.allocElementString(sections.strs, reorder.from, reorder.order, reorder.tertiary, reorder.tertiary_base, reorder.prebase); + result.before = sections.elem.allocElementString(sections.strs, reorder.before); + return result; + } + + private compileReorders(sections: GlobalSections, reorders: LKReorders): Ordr { + let result = new Ordr(); + + if(reorders?.reorder) { + for(let reorder of reorders.reorder) { + result.items.push(this.compileReorder(sections, reorder)); + } + } + + return result; + } + + public compile(sections: GlobalSections): Ordr { + return this.compileReorders(sections, this.keyboard.reorders); + } +} diff --git a/developer/src/kmc-keyboard/src/compiler/section-compiler.ts b/developer/src/kmc-keyboard/src/compiler/section-compiler.ts new file mode 100644 index 00000000000..a96d9351903 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/section-compiler.ts @@ -0,0 +1,29 @@ +import { LDMLKeyboard, KMXPlus, CompilerCallbacks } from "@keymanapp/common-types"; +import { SectionIdent } from '@keymanapp/ldml-keyboard-constants'; + +/* istanbul ignore next */ +export class SectionCompiler { + protected readonly keyboard: LDMLKeyboard.LKKeyboard; + protected readonly callbacks: CompilerCallbacks; + + constructor(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { + this.keyboard = source.keyboard; + this.callbacks = callbacks; + } + + public get id(): SectionIdent { + return null; + } + + public get required(): boolean { + return true; + } + + public compile(sections: KMXPlus.GlobalSections): KMXPlus.Section { + return null; + } + + public validate(): boolean { + return true; + } +} diff --git a/developer/src/kmc-keyboard/src/compiler/touch-layout-compiler.ts b/developer/src/kmc-keyboard/src/compiler/touch-layout-compiler.ts new file mode 100644 index 00000000000..311f2137495 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/touch-layout-compiler.ts @@ -0,0 +1,109 @@ +import { TouchLayout, LDMLKeyboard } from "@keymanapp/common-types"; + +export class TouchLayoutCompiler { + public compileToJavascript(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): TouchLayout.TouchLayoutFile { + let result: TouchLayout.TouchLayoutFile = {}; + + // start with desktop to mimic vk emit + result.desktop = { + defaultHint: "none", // TODO-LDML this should be optional + layer: [] + }; + + for(let layers of source.keyboard.layers) { + for(let layer of layers.layer) { + const resultLayer = this.compileHardwareLayer(source, result, layer); + result.desktop.layer.push(resultLayer); + } + } + return result; + } + + private compileHardwareLayer( + source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, + file: TouchLayout.TouchLayoutFile, + layer: LDMLKeyboard.LKLayer + ) { + // TODO-LDML: consider consolidation with keys.ts? + + let fileLayer: TouchLayout.TouchLayoutLayer = { + id: this.translateLayerIdToTouchLayoutLayerId(layer.id, layer.modifier), + row: [] + }; + + let y = -1; + + for(let row of layer.row) { + y++; + + let fileRow: TouchLayout.TouchLayoutRow = {id: y, key: []}; + fileLayer.row.push(fileRow); + + const keys = row.keys.split(' '); + for(let key of keys) { + const keydef = source.keyboard.keys?.key?.find(x => x.id == key); + if(keydef) { + const fileKey: TouchLayout.TouchLayoutKey = { + id: this.translateKeyIdentifierToTouch(keydef.id) as TouchLayout.TouchLayoutKeyId, + text: keydef.to || '', + // TODO-LDML: additional properties + }; + fileRow.key.push(fileKey); + } else { + // TODO-LDML: consider logging missing keys + } + } + } + + return fileLayer; + } + + private translateLayerIdToTouchLayoutLayerId(id: string, modifier: string): string { + // Touch layout layers have a set of reserved names that correspond to + // hardware modifiers. We want to map these identifiers first before falling + // back to the layer ids + + // The set of recognized layer identifiers is: + // + // touch | LDML + // ---------------+------------- + // default | none + // shift | shift + // caps | caps + // rightalt | altR + // rightalt-shift | altR shift + // + + const map = { + none: 'default', + shift: 'shift', + caps: 'caps', + altR: 'rightalt', + "altR shift": 'rightalt-shift' + }; + + // canonicalize modifier string, alphabetical + modifier = (modifier||'').split(/\b/).sort().join(' ').trim(); + + if(Object.hasOwn(map, modifier)) { + return (map as any)[modifier]; + } + + // TODO-LDML: Other layer names will be used unmodified, is this sufficient? + return id; + } + + private translateKeyIdentifierToTouch(id: string): string { + // Note: keys identifiers in kmx were traditionally case-insensitive, but we + // are going to use them as case-insensitive for LDML keyboards. The set of + // standard key identifiers a-z, A-Z, 0-9 will be used, where possible, and + // all other keys will be mapped to `T_key`. + + if(id.match(/^[0-9a-zA-Z]$/)) { + return 'K_'+id; + } + + // Not a standard key + return 'T_'+id; + } +} \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/compiler/tran.ts b/developer/src/kmc-keyboard/src/compiler/tran.ts new file mode 100644 index 00000000000..428dbd64f77 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/tran.ts @@ -0,0 +1,119 @@ +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { KMXPlus, LDMLKeyboard, CompilerCallbacks } from '@keymanapp/common-types'; +import { SectionCompiler } from "./section-compiler.js"; + +import Finl = KMXPlus.Finl; +import FinlItem = KMXPlus.FinlItem; +import Bksp = KMXPlus.Bksp; +import BkspItem = KMXPlus.BkspItem; +import GlobalSections = KMXPlus.GlobalSections; +import Tran = KMXPlus.Tran; +import TranItem = KMXPlus.TranItem; +import TranItemFlags = KMXPlus.TranItemFlags; +import LDMLKeyboardXMLSourceFile = LDMLKeyboard.LDMLKeyboardXMLSourceFile; +import LKTransform = LDMLKeyboard.LKTransform; +import LKTransforms = LDMLKeyboard.LKTransforms; + +type TransformCompilerType = 'simple' | 'final' | 'backspace'; + +class TransformCompiler extends SectionCompiler { + + protected type: T; + + constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { + super(source, callbacks); + } + + public validate(): boolean { + let valid = true; + // TODO-LDML: linting here should check for identical before+from, but this involves a double-parse which is ugly + // TODO-LDML: unicodesets means that either we fully parse them and verify conflicting rules or the linting is imperfect + return valid; + } + + protected newTranItem(): TranItemBase { + return null; + } + + protected newTran(): TranBase { + return null; + } + + private compileTransform(sections: GlobalSections, transform: LKTransform): TranItemBase { + let result = this.newTranItem(); + result.from = sections.elem.allocElementString(sections.strs, transform.from); + result.to = sections.strs.allocAndUnescapeString(transform.to); + result.before = sections.elem.allocElementString(sections.strs, transform.before); + result.flags = transform.error == 'fail' ? TranItemFlags.error : TranItemFlags.none; + return result; + } + + private compileTransforms(sections: GlobalSections, transforms: LKTransforms): TranBase { + let result = this.newTran(); + + if(transforms?.transform) { + for(let transform of transforms.transform) { + result.items.push(this.compileTransform(sections, transform)); + } + } + + return result; + } + + public compile(sections: GlobalSections): TranBase { + for(let t of this.keyboard.transforms) { + if(t.type == this.type) { + return this.compileTransforms(sections, t); + } + } + return this.newTran(); + } +} + +export class TranCompiler extends TransformCompiler<'simple', Tran, TranItem> { + constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { + super(source, callbacks); + this.type = 'simple'; + } + protected newTranItem(): TranItem { + return new TranItem(); + } + protected newTran(): Tran { + return new Tran(); + } + public get id() { + return constants.section.tran; + } +}; + +export class FinlCompiler extends TransformCompiler<'final', Finl, FinlItem> { + constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { + super(source, callbacks); + this.type = 'final'; + } + protected newTranItem(): FinlItem { + return new FinlItem(); + } + protected newTran(): Finl { + return new Finl(); + } + public get id() { + return constants.section.finl; + } +}; + +export class BkspCompiler extends TransformCompiler<'backspace', Bksp, BkspItem> { + constructor(source: LDMLKeyboardXMLSourceFile, callbacks: CompilerCallbacks) { + super(source, callbacks); + this.type = 'backspace'; + } + protected newTranItem(): BkspItem { + return new BkspItem(); + } + protected newTran(): Bksp { + return new Bksp(); + } + public get id() { + return constants.section.bksp; + } +}; diff --git a/developer/src/kmc-keyboard/src/compiler/visual-keyboard-compiler.ts b/developer/src/kmc-keyboard/src/compiler/visual-keyboard-compiler.ts new file mode 100644 index 00000000000..b80b590b9cf --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/visual-keyboard-compiler.ts @@ -0,0 +1,64 @@ +import { Constants, VisualKeyboard, LDMLKeyboard } from "@keymanapp/common-types"; + +export default class VisualKeyboardCompiler { + public compile(source: LDMLKeyboard.LDMLKeyboardXMLSourceFile): VisualKeyboard.VisualKeyboard { + let result = new VisualKeyboard.VisualKeyboard(); + + /* TODO-LDML: consider VisualKeyboardHeaderFlags.kvkhUseUnderlying kvkhDisplayUnderlying kvkhAltGr kvkh102 */ + result.header.flags = 0; + result.header.version = 0x0600; + + /* TODO-LDML: consider font, associatedKeyboard */ + result.header.associatedKeyboard = ''; + result.header.ansiFont.name = 'Arial'; + result.header.ansiFont.size = 10; + result.header.ansiFont.color = 0; + result.header.unicodeFont.name = 'Arial'; + result.header.unicodeFont.size = 10; + result.header.unicodeFont.color = 0; + + for(let layers of source.keyboard.layers) { + for(let layer of layers.layer) { + this.compileHardwareLayer(source, result, layer); + } + } + return result; + } + + private compileHardwareLayer( + source: LDMLKeyboard.LDMLKeyboardXMLSourceFile, + vk: VisualKeyboard.VisualKeyboard, + layer: LDMLKeyboard.LKLayer + ) { + // TODO-LDML: consider consolidation with keys.ts? + const shift = this.translateLayerIdToVisualKeyboardShift(layer.id); + + let y = -1; + for(let row of layer.row) { + y++; + + const keys = row.keys.split(' '); + let x = -1; + for(let key of keys) { + x++; + + let keydef = source.keyboard.keys?.key?.find(x => x.id == key); + + vk.keys.push({ + flags: VisualKeyboard.VisualKeyboardKeyFlags.kvkkUnicode, + shift: shift, + text: keydef.to, // TODO-LDML: displays + vkey: Constants.USVirtualKeyMap[y][x] // TODO-LDML: #7965 US-only + }); + } + } + } + + private translateLayerIdToVisualKeyboardShift(id: string) { + if(id == 'base') { + return 0; + } + // TODO-LDML: other modifiers + return 0; + } +} diff --git a/developer/src/kmc-keyboard/src/compiler/vkey.ts b/developer/src/kmc-keyboard/src/compiler/vkey.ts new file mode 100644 index 00000000000..c9b58b693c6 --- /dev/null +++ b/developer/src/kmc-keyboard/src/compiler/vkey.ts @@ -0,0 +1,70 @@ +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 get required(): boolean { + return !!this.keyboard.vkeys; + } + + public validate(): boolean { + let valid = true; + if(this.keyboard.vkeys) { + let from: string[] = [], to: string[] = []; + + this.keyboard.vkeys.vkey.forEach(vk => { + if(LdmlVkeyNames[vk.from] === undefined) { + this.callbacks.reportMessage(CompilerMessages.Error_VkeyIsNotValid({vkey: vk.from})); + valid = false; + } + + if(LdmlVkeyNames[vk.to] === undefined) { + this.callbacks.reportMessage(CompilerMessages.Error_VkeyIsNotValid({vkey: vk.to})); + valid = false; + } + + 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.keyboard.vkeys) { + return result; + } + + result.vkeys = this.keyboard.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-keyboard/src/main.ts b/developer/src/kmc-keyboard/src/main.ts new file mode 100644 index 00000000000..3bf1c37f1c4 --- /dev/null +++ b/developer/src/kmc-keyboard/src/main.ts @@ -0,0 +1,10 @@ + +export { default as Compiler } from './compiler/compiler.js'; +export { KeymanWebCompiler } from './compiler/keymanweb-compiler.js'; +export { default as VisualKeyboardCompiler } from './compiler/visual-keyboard-compiler.js'; +export { TouchLayoutCompiler } from './compiler/touch-layout-compiler.js'; +export { default as CompilerOptions } from './compiler/compiler-options.js'; +export { CompilerMessages } from './compiler/messages.js'; +export { default as KMXPlusMetadataCompiler } from './compiler/metadata-compiler.js'; + +export { KMXBuilder } from "@keymanapp/common-types"; diff --git a/developer/src/kmc-keyboard/src/util/util.ts b/developer/src/kmc-keyboard/src/util/util.ts new file mode 100644 index 00000000000..7caad74e3a3 --- /dev/null +++ b/developer/src/kmc-keyboard/src/util/util.ts @@ -0,0 +1,91 @@ +import { LDMLKeyboard } from "@keymanapp/common-types"; +import { constants } from "@keymanapp/ldml-keyboard-constants"; +/** + * Verifies that value is an item in the enumeration. + */ +export function isValidEnumValue(enu: T, value: string) { + return (Object.values(enu) as string[]).includes(value); +} + +/** + * Returns unique LKKeys only, preserving later ones + * in case of conflict. (i.e. later overrides) + * @param keys list of keys to consider. (mutated) + * @returns Array of unique keys. Order is not specified. + */ +export function calculateUniqueKeys(keys?: LDMLKeyboard.LKKey[]): LDMLKeyboard.LKKey[] { + if (!keys) { + return []; + } + // Need 'newer' (later) keys to override older ones. + const reverseKeys = keys.reverse(); // newest to oldest + const alreadySeen = new Set(); + // filter out only the keys that haven't already been seen + const uniqueKeys = reverseKeys.filter(({ id }) => { + if (!alreadySeen.has(id)) { + alreadySeen.add(id); + return true; + } + return false; + }); + + return uniqueKeys; +} + +/** + * + * @param layersList list of layers elements, from `keyboard?.layers` + * @returns set of key IDs + */ +export function allUsedKeyIdsInLayers(layersList : LDMLKeyboard.LKLayers[] | null): Set { + const s = new Set(); + if (layersList) { + for (const layers of layersList || []) { + for (const layer of layers.layer || []) { + for (const row of layer.row || []) { + if (row.keys) { + for (const k of row.keys.split(" ")) { + s.add(k); + } + } + } + } + } + } + return s; +} + +/** + * Determine modifier from layer info + * @param layer layer obj + * @returns modifier + */ +export function translateLayerAttrToModifier(layer: LDMLKeyboard.LKLayer) : number { + const { modifier } = layer; + if (modifier) { + let mod = constants.keys_mod_none; + for (let str of modifier.split(' ')) { + const submod = constants.keys_mod_map.get(str); + mod |= submod; + } + return mod; + } + // TODO-LDML: other modifiers, other ids? + return constants.keys_mod_none; +} + +/** + * @param modifier modifier sequence such as undefined, "none", "shift altR" etc + * @returns true if valid + */ +export function validModifier(modifier?: string) : boolean { + if (!modifier) return true; // valid to have no modifier, == none + for (let str of modifier.split(' ')) { + if (!constants.keys_mod_map.has(str)) { + return false; + } + } + return true; +} + + diff --git a/developer/src/kmc-keyboard/test/README.md b/developer/src/kmc-keyboard/test/README.md new file mode 100644 index 00000000000..50e58784d29 --- /dev/null +++ b/developer/src/kmc-keyboard/test/README.md @@ -0,0 +1,7 @@ +Keyman LDML Keyboard Compiler Tests +=================================== + +Test +---- + + ../build.sh test diff --git a/developer/src/kmc-keyboard/test/fixtures/basic-kvk.txt b/developer/src/kmc-keyboard/test/fixtures/basic-kvk.txt new file mode 100644 index 00000000000..341beb3597b --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/basic-kvk.txt @@ -0,0 +1,53 @@ +# +# basic-kvk.txt describes the expected output of running kmc against basic.xml to generate +# a .kvk file. It is used in the end-to-end test test-visual-keyboard-compiler-e2e.ts. +# +# Any changes to the compiler or basic.xml will likely result in changes to the compiled file. +# + +block(kvkheader) + 4b 56 4b 46 # identifier 'KVKF' + 00 06 00 00 # version 0x0600 + 00 # flags +block(associated_keyboard) + 01 00 # string len in UTF-16 code units incl zterm + 00 00 # '\0' + +block(ansi_font) + 06 00 # string len in UTF-16 code units incl zterm + 41 00 72 00 69 00 61 00 6c 00 00 00 # 'Arial\0' + +block(ansi_font_extra) + 0a 00 00 00 # size + 00 00 00 00 # color + +block(unicode_font) + 06 00 # string len in UTF-16 code units incl zterm + 41 00 72 00 69 00 61 00 6c 00 00 00 # 'Arial\0' + +block(unicode_font_extra) + 0a 00 00 00 # size + 00 00 00 00 # color + +block(keys_header) + 02 00 00 00 # number of keys + +block(keys) + +block(key0) + 02 # flags = unicode + 00 00 # shift + c0 00 # vkey + 02 00 # string len in UTF-16 code units incl zterm + 27 01 00 00 # "ħ" + 00 00 00 00 # bitmap + +block(key1) + 02 # flags = unicode + 00 00 # shift + 31 00 # vkey + 03 00 # string len in UTF-16 code units incl zterm + 90 17 b6 17 00 00 # "ថា" + 00 00 00 00 # bitmap + +block(eof) # end of file \ No newline at end of file diff --git a/developer/src/kmc-keyboard/test/fixtures/basic-no-debug.js b/developer/src/kmc-keyboard/test/fixtures/basic-no-debug.js new file mode 100644 index 00000000000..177d55963ee --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/basic-no-debug.js @@ -0,0 +1 @@ +if(typeof keyman === 'undefined') {console.error('Keyboard requires KeymanWeb 16.0 or later');} else {KeymanWeb.KR(new Keyboard_basic());}function Keyboard_basic() {this.KI="Keyboard_basic";this.KN={"value":"TestKbd"};this.KMINVER="16.0";this.KV={F: '10pt "Arial"', K102: 0};this.KV.KLS={TODO_LDML: 2};this.KDU=1;this.KH="";this.KM=0;this.KBVER="1.0.0";this.KMBM=0;this.KVKL={"desktop":{"defaultHint":"none","layer":[{"id":"base","row":[{"id":0,"key":[{"id":"T_hmaqtugha","text":"ħ"},{"id":"T_that","text":"ថា"}]}]}]}};this.gs=function(t,e){return 0;};} \ No newline at end of file diff --git a/developer/src/kmc-keyboard/test/fixtures/basic.js b/developer/src/kmc-keyboard/test/fixtures/basic.js new file mode 100644 index 00000000000..59a1902ee67 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/basic.js @@ -0,0 +1,47 @@ +if(typeof keyman === 'undefined') { + console.error('Keyboard requires KeymanWeb 16.0 or later'); +} else { + KeymanWeb.KR(new Keyboard_basic()); +} +function Keyboard_basic() { + this.KI="Keyboard_basic"; + this.KN={"value":"TestKbd"}; + this.KMINVER="16.0"; + this.KV={F: '10pt "Arial"', K102: 0}; + this.KV.KLS={ + TODO_LDML: 2 + }; + this.KDU=1; + this.KH=""; + this.KM=0; + this.KBVER="1.0.0"; + this.KMBM=0; + this.KVKL={ + "desktop": { + "defaultHint": "none", + "layer": [ + { + "id": "base", + "row": [ + { + "id": 0, + "key": [ + { + "id": "T_hmaqtugha", + "text": "ħ" + }, + { + "id": "T_that", + "text": "ថា" + } + ] + } + ] + } + ] + } +}; + this.gs=function(t,e){ + return 0; + }; +} diff --git a/developer/src/kmc-keyboard/test/fixtures/basic.txt b/developer/src/kmc-keyboard/test/fixtures/basic.txt new file mode 100644 index 00000000000..0b9077e38ef --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/basic.txt @@ -0,0 +1,509 @@ +# +# basic.txt describes the expected output of running kmc against basic.xml. It is used in +# the end-to-end test test-compiler-e2e.ts. +# +# Any changes to the compiler or basic.xml will likely result in changes to the compiled file. +# While structural differences should be updated manually in this file to ensure that we are +# getting the expected result for the e2e test, the checksum can be safely retrieved from the +# updated compilation result. The following may be helpful for working with this file when +# updating the binary format: +# +# cd developer/src/kmc +# ./build.sh configure build # if needed +# ./build.sh build-fixtures +# +# This will compile both the .xml and the .txt to build/test/fixtures and also emit the +# checksum for basic-xml.kmx so you can patch that into this file. +# + +block(kmxheader) # struct COMP_KEYBOARD { + 4b 58 54 53 # KMX_DWORD dwIdentifier; // 0000 Keyman compiled keyboard id + + 00 10 00 00 # KMX_DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400 + + 00 00 00 00 # KMX_DWORD dwCheckSum; // 0008 deprecated in 16.0, always 0 + 00 00 00 00 # KMX_DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts + 01 00 00 00 # KMX_DWORD IsRegistered; // 0010 + 00 00 00 00 # KMX_DWORD version; // 0014 keyboard version + + sizeof(stores,12) # KMX_DWORD cxStoreArray; // 0018 in array entries + 00 00 00 00 # KMX_DWORD cxGroupArray; // 001C in array entries + + offset(stores) # KMX_DWORD dpStoreArray; // 0020 [LPSTORE] address of first item in store array + 00 00 00 00 # KMX_DWORD dpGroupArray; // 0024 [LPGROUP] address of first item in group array + + ff ff ff ff # KMX_DWORD StartGroup[2]; // 0028 index of starting groups [2 of them] + ff ff ff ff # + + 20 00 00 00 # KMX_DWORD dwFlags; // 0030 Flags for the keyboard file + + 00 00 00 00 # KMX_DWORD dwHotKey; // 0034 standard windows hotkey (hiword=shift/ctrl/alt stuff, loword=vkey) + + 00 00 00 00 # KMX_DWORD dpBitmapOffset; // 0038 offset of the bitmaps in the file + 00 00 00 00 # KMX_DWORD dwBitmapSize; // 003C size in bytes of the bitmaps + # }; + +block(kmxplusinfo) # struct COMP_KEYBOARD_KMXPLUSINFO { + offset(sect) # KMX_DWORD dpKMXPlus; // 0040 offset of KMXPlus data from BOF, header is first + diff(sect,eof) # KMX_DWORD dwKMXPlusSize; // 0044 size in bytes of entire KMXPlus data + # }; + +block(stores) # struct COMP_STORE { + 07 00 00 00 # KMX_DWORD dwSystemID; - TSS_NAME + offset(store_name_name) # KMX_DWORD dpName; + offset(store_name_string) # KMX_DWORD dpString; + # }; + # excluding this so we don't have compiled version changes + # 14 00 00 00 # KMX_DWORD dwSystemID; - TSS_COMPILEDVERSION + # offset(store_compiledversion_name) # KMX_DWORD dpName; + # offset(store_compiledversion_string) # KMX_DWORD dpString; + # }; + 24 00 00 00 # KMX_DWORD dwSystemID; - TSS_KEYBOARDVERSION + offset(store_keyboardversion_name) # KMX_DWORD dpName; + offset(store_keyboardversion_string) # KMX_DWORD dpString; + # }; + 26 00 00 00 # KMX_DWORD dwSystemID; - TSS_TARGETS + offset(store_targets_name) # KMX_DWORD dpName; + offset(store_targets_string) # KMX_DWORD dpString; + # }; + +block(store_name_name) + 26 00 4e 00 41 00 4d 00 45 00 00 00 # '&NAME' +block(store_name_string) + 54 00 65 00 73 00 74 00 4b 00 62 00 64 00 00 00 # 'TestKbd' + +block(store_keyboardversion_name) + 26 00 4b 00 45 00 59 00 42 00 4f 00 41 00 52 00 + 44 00 56 00 45 00 52 00 53 00 49 00 4f 00 4e 00 + 00 00 # '&KEYBOARDVERSION' +block(store_keyboardversion_string) + 31 00 2e 00 30 00 2e 00 30 00 00 00 # '1.0.0' + +block(store_targets_name) + 26 00 54 00 41 00 52 00 47 00 45 00 54 00 53 00 00 00 # '&TARGETS' +block(store_targets_string) + 64 00 65 00 73 00 6b 00 74 00 6f 00 70 00 00 00 # 'desktop' + +block(sect) # struct COMP_KMXPLUS_SECT { + 73 65 63 74 # KMX_DWORD header.ident; // 0000 Section name + diff(sect,endsect) # KMX_DWORD header.size; // 0004 Section length + diff(sect,eof) # KMX_DWORD total; // 0008 KMXPlus entire length + sizeof(sectitems,8) # KMX_DWORD count; // 000C number of section headers + # }; + # Next sections are sect entries + # KMX_DWORD sect; // 0010+ Section identity + # KMX_DWORD offset; // 0014+ Section offset relative to dpKMXPlus of section + +block(sectitems) + 62 6b 73 70 + diff(sect,bksp) + + 64 69 73 70 + diff(sect,disp) + + 65 6c 65 6d + diff(sect,elem) + + 66 69 6e 6c + diff(sect,finl) + + 6b 65 79 73 + diff(sect,keys) + + 6c 61 79 72 + diff(sect,layr) + + 6c 69 73 74 + diff(sect,list) + + 6c 6f 63 61 + diff(sect,loca) + + 6d 65 74 61 + diff(sect,meta) + + 6e 61 6d 65 + diff(sect,name) + + 6f 72 64 72 + diff(sect,ordr) + + 73 74 72 73 + diff(sect,strs) + + 74 72 61 6e + diff(sect,tran) + + 76 6b 65 79 + diff(sect,vkey) + +block(endsect) + +# ---------------------------------------------------------------------------------------------------- +# bksp +# ---------------------------------------------------------------------------------------------------- + +block(bksp) + 62 6b 73 70 + sizeof(bksp) + 01 00 00 00 # KMX_DWORD count; // 0008 number of entries + # }; + + # struct COMP_KMXPLUS_TRAN_ENTRY { + index(elemNull,elemBkspFrom) # KMXPLUS_ELEM from; + 00 00 00 00 # KMXPLUS_STR to; + 00 00 00 00 # KMXPLUS_ELEM before; + 00 00 00 00 # KMX_DWORD flags; + # }; + + +# ---------------------------------------------------------------------------------------------------- +# disp +# ---------------------------------------------------------------------------------------------------- + +block(disp) # struct COMP_KMXPLUS_DISP { + 64 69 73 70 # KMX_DWORD header.ident; // 0000 Section name - disp + sizeof(disp) # KMX_DWORD header.size; // 0004 Section length + 01 00 00 00 # KMX_DWORD count; // 0008 number of entries + index(strNull,strElemBkspFrom2,2) # KMX_DWORD baseCharacter // 000C baseCharacter = 'e' + # }; + + # only entry: + index(strNull,strElemTranFrom2,2) # KMX_DWORD to // 000C baseCharacter = 'a' + index(strNull,strElemTranFrom1,2) # KMX_DWORD display // 000C baseCharacter = '^' + + + +# ---------------------------------------------------------------------------------------------------- +# elem +# ---------------------------------------------------------------------------------------------------- + +block(elem) # struct COMP_KMXPLUS_ELEM { + 65 6c 65 6d # KMX_DWORD header.ident; // 0000 Section name - elem + diff(elem,endelem) # KMX_DWORD header.size; // 0004 Section length + index(elemNull,endelem) # KMX_DWORD count; // 0008 number of entries + # }; + + 00 00 00 00 # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemNull,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + diff(elem,elemFinlFrom) # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemFinlFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + diff(elem,elemTranFrom) # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemTranFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + diff(elem,elemBkspFrom) # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemBkspFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + diff(elem,elemOrdrFrom) # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemOrdrFrom,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + diff(elem,elemOrdrBefore) # KMX_DWORD offset; // 0010+ offset from this blob + sizeof(elemOrdrBefore,8) # KMX_DWORD length; // 0014+ str length (ELEMENT units) + + +block(elemNull) + +block(elemFinlFrom) + index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset + index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset + +block(elemTranFrom) + index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset + index(strNull,strElemTranFrom2,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset + +block(elemBkspFrom) + index(strNull,strElemTranFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset + index(strNull,strElemBkspFrom2,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset + +block(elemOrdrFrom) + # + index(strNull,strElemOrdrFrom1,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 0A 00 # KMX_DWORD flags; // flag and order values - unicodeset + index(strNull,strElemOrdrFrom2,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 37 00 # KMX_DWORD flags; // flag and order values - unicodeset + index(strNull,strElemOrdrFrom3,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 0A 00 # KMX_DWORD flags; // flag and order values - unicodeset + +block(elemOrdrBefore) + index(strNull,strElemOrdrBefore,2) # KMX_DWORD element; // str: output string or UTF-32LE codepoint + 01 00 00 00 # KMX_DWORD flags; // flag and order values - unicodeset + +block(endelem) + +# ---------------------------------------------------------------------------------------------------- +# finl +# ---------------------------------------------------------------------------------------------------- + +block(finl) + 66 69 6e 6c + sizeof(finl) + 01 00 00 00 # KMX_DWORD count; // 0008 number of entries + # }; + + # struct COMP_KMXPLUS_TRAN_ENTRY { + index(elemNull,elemFinlFrom) # KMXPLUS_ELEM from; + index(strNull,strElemTranFrom1,2) # KMXPLUS_STR to; + 00 00 00 00 # KMXPLUS_ELEM before; + 01 00 00 00 # KMX_DWORD flags; + # }; +# ---------------------------------------------------------------------------------------------------- +# keys +# ---------------------------------------------------------------------------------------------------- + +block(keys) # struct COMP_KMXPLUS_KEYS { + 6b 65 79 73 # KMX_DWORD header.ident; // 0000 Section name - keys + sizeof(keys) # KMX_DWORD header.size; // 0004 Section length + 02 00 00 00 # KMX_DWORD keyCount + 01 00 00 00 # KMX_DWORD flicksCount + 00 00 00 00 # KMX_DWORD flickCount + 02 00 00 00 # KMX_DWORD kmapCount; // 0008 number of kmap + # keys + # (#0000) hmaqtugha + index(strNull,strKey1,2) # KMXPLUS_STR 'U+0127' + 01 00 00 00 # KMX_DWORD (flags: extend) + index(strNull,strHmaqtugha,2) # KMXPLUS_STR 'hmaqtugha' + 00 00 00 00 # KMXPLUS_STR switch + 0A 00 00 00 # KMX_DWORD width*10 + 01 00 00 00 # TODO: index(listNull,indexAe,4) # LIST longPress 'a e' + 00 00 00 00 # STR longPressDefault + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST multiTap + 00 00 00 00 # flicks 0 + # (#0001) that + index(strNull,strKeys,2) # KMXPLUS_STR 'U+0127' + 01 00 00 00 # KMX_DWORD flags = extend + index(strNull,strThat,2) # KMXPLUS_STR 'hmaqtugha' + 00 00 00 00 # KMXPLUS_STR switch + 0A 00 00 00 # KMX_DWORD width*10 + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST longPress + 00 00 00 00 # STR longPressDefault + 00 00 00 00 # TODO: index(listNull,listNull,4) # LIST multiTap + 00 00 00 00 # flicks 0 + # flicks + # flicks 0 - null + 00 00 00 00 # KMX_DWORD count + 00 00 00 00 # KMX_DWORD flick + 00 00 00 00 # KMX_STR id + # flick + # Right now there aren't any flick elements. + #00 00 00 00 # LIST directions + #00 00 00 01 # flags + #00 00 00 00 # str: to + # kmapdata: + + 31 00 00 00 00 00 00 00 01 00 00 00 # KMX_DWORD vkey, mod, key; + c0 00 00 00 00 00 00 00 00 00 00 00 # KMX_DWORD vkey, mod, key; + + +# ---------------------------------------------------------------------------------------------------- +# layr +# ---------------------------------------------------------------------------------------------------- + +block(layr) # struct COMP_KMXPLUS_LAYR { + 6c 61 79 72 # KMX_DWORD header.ident; // 0000 Section name - layr + sizeof(layr) # KMX_DWORD header.size; // 0004 Section length + 01 00 00 00 # KMX_DWORD listCount + 01 00 00 00 # KMX_DWORD layerCount + 01 00 00 00 # KMX_DWORD rowCount + 02 00 00 00 # KMX_DWORD keyCount + # list 0 + 04 00 00 00 # KMX_DWORD hardware = 'us' + 00 00 00 00 # KMX_DWORD layer; + 01 00 00 00 # count + 7B 00 00 00 # KMX_DWORD minDeviceWidth; // 123 + # layers 0 + index(strNull,strBase,2) # KMXPLUS_STR id; + 00 00 00 00 # KMX_DWORD mod + 00 00 00 00 # KMX_DWORD row index + 01 00 00 00 # KMX_DWORD count + # rows 0 + 00 00 00 00 # KMX_DWORD key index + 02 00 00 00 # KMX_DWORD count + # keys + index(strNull,strHmaqtugha,2) # KMXPLUS_STR locale; // 'hmaqtugha' + index(strNull,strThat,2) # KMXPLUS_STR locale; // 'that' + +# ---------------------------------------------------------------------------------------------------- +# list +# ---------------------------------------------------------------------------------------------------- + +# TODO-LDML: lots of comment-out ahead. Need to revisit. + +block(list) # struct COMP_KMXPLUS_LAYR_LIST { + 6c 69 73 74 # KMX_DWORD header.ident; // 0000 Section name - list + diff(list,endList) # KMX_DWORD header.size; // 0004 Section length + 02 00 00 00 # KMX_DWORD listCount (should be 2) + 02 00 00 00 # KMX_DWORD indexCount (should be 2) + # list #0 the null list + block(listNull) + 00 00 00 00 #index(indexNull,indexNull,2) # KMX_DWORD list index (0) + 00 00 00 00 # KMX_DWORD lists[0].count + # list #1 the ae list + block(listAe) + 00 00 00 00 # index(indexAe,indexNull,2) # KMX_DWORD list index (also 0) + 02 00 00 00 # KMX_DWORD count + block(endLists) + # indices + #block(indexNull) + # No null index + # index(strNull,strNull,2) # KMXPLUS_STR string index + block(indexAe) + index(strNull,strElemTranFrom2,2) # KMXPLUS_STR a + index(strNull,strElemBkspFrom2,2) # KMXPLUS_STR e + block(endIndices) + block(endList) + +# ---------------------------------------------------------------------------------------------------- +# loca +# ---------------------------------------------------------------------------------------------------- + +block(loca) # struct COMP_KMXPLUS_LOCA { + 6c 6f 63 61 # KMX_DWORD header.ident; // 0000 Section name - loca + sizeof(loca) # KMX_DWORD header.size; // 0004 Section length + 01 00 00 00 # KMX_DWORD count; // 0008 number of locales + index(strNull,strLocale,2) # KMXPLUS_STR locale; // 000C+ locale string entry = 'mt' + # }; +# ---------------------------------------------------------------------------------------------------- +# meta +# ---------------------------------------------------------------------------------------------------- + +block(meta) # struct COMP_KMXPLUS_META { + 6d 65 74 61 # KMX_DWORD header.ident; // 0000 Section name - meta + sizeof(meta) # KMX_DWORD header.size; // 0004 Section length + index(strNull,strAuthor,2) # KMXPLUS_STR author; + index(strNull,strConformsTo,2) # KMXPLUS_STR conform; + index(strNull,strLayout,2) # KMXPLUS_STR layout; + index(strNull,strNorm,2) # KMXPLUS_STR normalization; + index(strNull,strIndicator,2) # KMXPLUS_STR indicator; + index(strNull,strVersion,2) # KMXPLUS_STR version; + 00 00 00 00 # KMX_DWORD settings; + # }; +# ---------------------------------------------------------------------------------------------------- +# store_targets_name +# ---------------------------------------------------------------------------------------------------- + +block(name) # struct COMP_KMXPLUS_META { + 6e 61 6d 65 # KMX_DWORD header.ident; // 0000 Section name - name + sizeof(name) # KMX_DWORD header.size; // 0004 Section length + 01 00 00 00 # KMX_DWORD count; // 0008 number of names + index(strNull,strName,2) # KMXPLUS_STR name; // 000C+ name string entry = 'TestKbd' + # }; + +# ---------------------------------------------------------------------------------------------------- +# ordr +# ---------------------------------------------------------------------------------------------------- + +block(ordr) + 6f 72 64 72 # KMX_DWORD header.ident; // 0000 Section name - ordr + sizeof(ordr) # KMX_DWORD header.size; // 0004 Section length + 01 00 00 00 # KMX_DWORD count; // 0008 count of str entries + # }; + + index(elemNull,elemOrdrFrom) # KMXPLUS_ELEM elements; + index(elemNull,elemOrdrBefore) # KMXPLUS_ELEM before; + +# ---------------------------------------------------------------------------------------------------- +# strs +# ---------------------------------------------------------------------------------------------------- + +block(strs) # struct COMP_KMXPLUS_STRS { + 73 74 72 73 # KMX_DWORD header.ident; // 0000 Section name - strs + diff(strs,endstrs) # KMX_DWORD header.size; // 0004 Section length + index(strNull,endstrs,2) # KMX_DWORD count; // 0008 count of str entries + # }; + + # Next sections are string entries + # KMX_DWORD offset; // 0010+ offset from this blob + # KMX_DWORD length; // 0014+ str length (UTF-16LE units) + + diff(strs,strNull) sizeof(strNull,2) + diff(strs,strVersion) sizeof(strVersion,2) + diff(strs,strNorm) sizeof(strNorm,2) + diff(strs,strName) sizeof(strName,2) + diff(strs,strElemTranFrom1) sizeof(strElemTranFrom1,2) + diff(strs,strElemTranFrom2) sizeof(strElemTranFrom2,2) + diff(strs,strBase) sizeof(strBase,2) + diff(strs,strElemBkspFrom2) sizeof(strElemBkspFrom2,2) + diff(strs,strHmaqtugha) sizeof(strHmaqtugha,2) + diff(strs,strLocale) sizeof(strLocale,2) + diff(strs,strLayout) sizeof(strLayout,2) + diff(strs,strAuthor) sizeof(strAuthor,2) + diff(strs,strConformsTo) sizeof(strConformsTo,2) + diff(strs,strThat) sizeof(strThat,2) + diff(strs,strTranTo) sizeof(strTranTo,2) + diff(strs,strKey1) sizeof(strKey1,2) + diff(strs,strKeys) sizeof(strKeys,2) + diff(strs,strElemOrdrFrom3) sizeof(strElemOrdrFrom3,2) + diff(strs,strElemOrdrFrom1) sizeof(strElemOrdrFrom1,2) + diff(strs,strElemOrdrBefore) sizeof(strElemOrdrBefore,2) + diff(strs,strElemOrdrFrom2) sizeof(strElemOrdrFrom2,2) + diff(strs,strIndicator) sizeof(strIndicator,2) + + + # String table -- block(x) is used to store the null u16char at end of each string + # without interfering with sizeof() calculation above + + block(strNull) block(x) 00 00 # the zero-length string + block(strVersion) 31 00 2e 00 30 00 2e 00 30 00 block(x) 00 00 # '1.0.0' + block(strNorm) 4e 00 46 00 43 00 block(x) 00 00 # 'NFC' + block(strName) 54 00 65 00 73 00 74 00 4b 00 62 00 64 00 block(x) 00 00 # 'TestKbd' + block(strElemTranFrom1) 5E 00 block(x) 00 00 # '^' + block(strElemTranFrom2) 61 00 block(x) 00 00 # 'a' + block(strBase) 62 00 61 00 73 00 65 00 block(x) 00 00 # 'base' + block(strElemBkspFrom2) 65 00 block(x) 00 00 # 'e' + block(strHmaqtugha) 68 00 6d 00 61 00 71 00 74 00 75 00 67 00 68 00 61 00 block(x) 00 00 # 'hmaqtugha' + block(strLocale) 6d 00 74 00 block(x) 00 00 # 'mt' + block(strLayout) 71 00 77 00 65 00 72 00 74 00 79 00 block(x) 00 00 # 'qwerty' + block(strAuthor) 73 00 72 00 6c 00 32 00 39 00 35 00 block(x) 00 00 # 'srl295' + block(strConformsTo) 74 00 65 00 63 00 68 00 70 00 72 00 65 00 76 00 + 69 00 65 00 77 00 block(x) 00 00 # 'techpreview' + block(strThat) 74 00 68 00 61 00 74 00 block(x) 00 00 # 'that' + block(strTranTo) E2 00 block(x) 00 00 # 'â' + block(strKey1) 27 01 block(x) 00 00 # 'ħ' + block(strKeys) 90 17 b6 17 block(x) 00 00 # 'ថា' + # + block(strElemOrdrFrom3) 45 1a block(x) 00 00 # 'ᩅ' + block(strElemOrdrFrom1) 60 1a block(x) 00 00 # '᩠' + block(strElemOrdrBefore) 6b 1a block(x) 00 00 # 'ᩫ' + block(strElemOrdrFrom2) 75 1a block(x) 00 00 # '᩵' + block(strIndicator) 3d d8 40 de block(x) 00 00 # '🙀' + + + + +block(endstrs) # end of strs block + +# ---------------------------------------------------------------------------------------------------- +# tran +# ---------------------------------------------------------------------------------------------------- + +block(tran) # struct COMP_KMXPLUS_TRAN { + 74 72 61 6e # KMX_DWORD header.ident; // 0000 Section name - tran + sizeof(tran) # KMX_DWORD header.size; // 0004 Section length + 01 00 00 00 # KMX_DWORD count; // 0008 number of entries + # }; + + # struct COMP_KMXPLUS_TRAN_ENTRY { + index(elemNull,elemTranFrom) # KMXPLUS_ELEM from; + index(strNull,strTranTo,2) # KMXPLUS_STR to; + 00 00 00 00 # KMXPLUS_ELEM before; + 00 00 00 00 # KMX_DWORD flags; + # }; + +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-keyboard/test/fixtures/basic.xml b/developer/src/kmc-keyboard/test/fixtures/basic.xml new file mode 100644 index 00000000000..32eeebaef17 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/basic.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/bksp/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/bksp/minimal.xml new file mode 100644 index 00000000000..00b466380d2 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/bksp/minimal.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/disp/escaped.xml b/developer/src/kmc-keyboard/test/fixtures/sections/disp/escaped.xml new file mode 100644 index 00000000000..56508009fda --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/disp/escaped.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/disp/invalid-dupto.xml b/developer/src/kmc-keyboard/test/fixtures/sections/disp/invalid-dupto.xml new file mode 100644 index 00000000000..3f4c264cece --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/disp/invalid-dupto.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/disp/maximal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/disp/maximal.xml new file mode 100644 index 00000000000..979b55e43cd --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/disp/maximal.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/disp/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/disp/minimal.xml new file mode 100644 index 00000000000..feb599c825f --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/disp/minimal.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/disp/options-only.xml b/developer/src/kmc-keyboard/test/fixtures/sections/disp/options-only.xml new file mode 100644 index 00000000000..c030909cc91 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/disp/options-only.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/disp/typical.xml b/developer/src/kmc-keyboard/test/fixtures/sections/disp/typical.xml new file mode 100644 index 00000000000..935225e214f --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/disp/typical.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/finl/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/finl/minimal.xml new file mode 100644 index 00000000000..80c22bd3fee --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/finl/minimal.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped.xml new file mode 100644 index 00000000000..c2cbd96c249 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped2.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped2.xml new file mode 100644 index 00000000000..dcd63b9cd61 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/escaped2.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/gap-switch.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/gap-switch.xml new file mode 100644 index 00000000000..6aae1db0b72 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/gap-switch.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware.xml new file mode 100644 index 00000000000..19bd0a89359 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_iso.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_iso.xml new file mode 100644 index 00000000000..6eeeb0bec38 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_iso.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_us.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_us.xml new file mode 100644 index 00000000000..de7f29acc54 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/hardware_us.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-bad-modifier.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-bad-modifier.xml new file mode 100644 index 00000000000..ea2eaedb442 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-bad-modifier.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-keys.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-keys.xml new file mode 100644 index 00000000000..82db56ee968 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-keys.xml @@ -0,0 +1,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-rows.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-rows.xml new file mode 100644 index 00000000000..466d06e779a --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-hardware-too-many-rows.xml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-key-missing-attrs.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-key-missing-attrs.xml new file mode 100644 index 00000000000..b58fed589c0 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-key-missing-attrs.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-flick.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-flick.xml new file mode 100644 index 00000000000..5faa918aa9b --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-flick.xml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-layer.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-layer.xml new file mode 100644 index 00000000000..dc233a14d04 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-missing-layer.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-undefined-key.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-undefined-key.xml new file mode 100644 index 00000000000..b3d01f4aa81 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/invalid-undefined-key.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/maximal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/maximal.xml new file mode 100644 index 00000000000..24a60cc94ea --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/maximal.xml @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/keys/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/keys/minimal.xml new file mode 100644 index 00000000000..c29bfa1fd91 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/keys/minimal.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-form.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-form.xml new file mode 100644 index 00000000000..064a59df80f --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-form.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-hardware.xml new file mode 100644 index 00000000000..ec574c7c563 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-invalid-hardware.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-hardware.xml new file mode 100644 index 00000000000..bbd4b58585b --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-hardware.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-layer.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-layer.xml new file mode 100644 index 00000000000..0a2cdf06105 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-missing-layer.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-multi-hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-multi-hardware.xml new file mode 100644 index 00000000000..39644279da8 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-multi-hardware.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-touch-hardware.xml b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-touch-hardware.xml new file mode 100644 index 00000000000..176d9b13f2e --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/layr/invalid-touch-hardware.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/loca/invalid-locale.xml b/developer/src/kmc-keyboard/test/fixtures/sections/loca/invalid-locale.xml new file mode 100644 index 00000000000..1644742c733 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/loca/invalid-locale.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/loca/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/loca/minimal.xml new file mode 100644 index 00000000000..7ca838be6fd --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/loca/minimal.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/loca/multiple.xml b/developer/src/kmc-keyboard/test/fixtures/sections/loca/multiple.xml new file mode 100644 index 00000000000..30d00bea2e1 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/loca/multiple.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-normalization.xml b/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-normalization.xml new file mode 100644 index 00000000000..13723cbb2e2 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-normalization.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-version-1.0.xml b/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-version-1.0.xml new file mode 100644 index 00000000000..f7246e40cbb --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-version-1.0.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-version-v1.0.3.xml b/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-version-v1.0.3.xml new file mode 100644 index 00000000000..dd6e292345c --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/meta/invalid-version-v1.0.3.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/meta/maximal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/meta/maximal.xml new file mode 100644 index 00000000000..473501457fd --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/meta/maximal.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/meta/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/meta/minimal.xml new file mode 100644 index 00000000000..9de105e868e --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/meta/minimal.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/name/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/name/minimal.xml new file mode 100644 index 00000000000..7181aa65f31 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/name/minimal.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/name/multiple.xml b/developer/src/kmc-keyboard/test/fixtures/sections/name/multiple.xml new file mode 100644 index 00000000000..8838c4b4a6b --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/name/multiple.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/ordr/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/ordr/minimal.xml new file mode 100644 index 00000000000..6ab49c6a5a2 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/ordr/minimal.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/tran/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/tran/minimal.xml new file mode 100644 index 00000000000..b6ff27db6dd --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/tran/minimal.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-from-vkey.xml b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-from-vkey.xml new file mode 100644 index 00000000000..d28c9f09d02 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-from-vkey.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-repeated-vkey.xml b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-repeated-vkey.xml new file mode 100644 index 00000000000..15641e6855a --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-repeated-vkey.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-to-vkey.xml b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-to-vkey.xml new file mode 100644 index 00000000000..ff475970009 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/invalid-to-vkey.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/vkey/minimal.xml b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/minimal.xml new file mode 100644 index 00000000000..3223d311893 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/minimal.xml @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/vkey/redundant.xml b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/redundant.xml new file mode 100644 index 00000000000..8d5d1caf617 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/redundant.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/sections/vkey/same-target.xml b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/same-target.xml new file mode 100644 index 00000000000..65aa1fa2a52 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/sections/vkey/same-target.xml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/fixtures/test-fr.json b/developer/src/kmc-keyboard/test/fixtures/test-fr.json new file mode 100644 index 00000000000..9e3341f8243 --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/test-fr.json @@ -0,0 +1,77 @@ +{ + "keyboardTest": { + "conformsTo": "techpreview", + "info": { + "keyboard": "fr-t-k0-azerty.xml", + "author": "Team Keyboard", + "name": "fr-test" + }, + "repertoire": [ + { + "name": "simple-repertoire", + "chars": "[a b c d e \\u{22}]", + "type": "simple" + }, + { + "name": "chars-repertoire", + "chars": "[á é ó]", + "type": "gesture" + } + ], + "tests": [ + { + "name": "key-tests", + "test": [ + { + "name": "key-test", + "startContext": { + "to": "abc\\u0022..." + }, + "actions": [ + { + "keystroke": { + "key": "s" + } + }, + { + "check": { + "result": "abc\\u0022...s" + } + }, + { + "keystroke": { + "key": "t" + } + }, + { + "check": { + "result": "abc\\u0022...st" + } + }, + { + "keystroke": { + "key": "u" + } + }, + { + "check": { + "result": "abc\\u0022...stu" + } + }, + { + "emit": { + "to": "v" + } + }, + { + "check": { + "result": "abc\\u0022...stuv" + } + } + ] + } + ] + } + ] + } +} diff --git a/developer/src/kmc-keyboard/test/fixtures/test-fr.xml b/developer/src/kmc-keyboard/test/fixtures/test-fr.xml new file mode 100644 index 00000000000..c97825385ad --- /dev/null +++ b/developer/src/kmc-keyboard/test/fixtures/test-fr.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-keyboard/test/helpers/index.ts b/developer/src/kmc-keyboard/test/helpers/index.ts new file mode 100644 index 00000000000..0c476313a8e --- /dev/null +++ b/developer/src/kmc-keyboard/test/helpers/index.ts @@ -0,0 +1,233 @@ +/** + * Helpers and utilities for the Mocha tests. + */ +import 'mocha'; +import * as path from 'path'; +import * as fs from 'fs'; +import { fileURLToPath } from 'url'; +import { SectionCompiler } from '../../src/compiler/section-compiler.js'; +import { KMXPlus, LDMLKeyboardXMLSourceFileReader, VisualKeyboard, CompilerEvent, CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; +import Compiler from '../../src/compiler/compiler.js'; +import { assert } from 'chai'; +import KMXPlusMetadataCompiler from '../../src/compiler/metadata-compiler.js'; +import CompilerOptions from '../../src/compiler/compiler-options.js'; +import VisualKeyboardCompiler from '../../src/compiler/visual-keyboard-compiler.js'; + +import KMXPlusFile = KMXPlus.KMXPlusFile; +import Elem = KMXPlus.Elem; +import GlobalSections = KMXPlus.GlobalSections; +import Section = KMXPlus.Section; +import Strs = KMXPlus.Strs; +import List = KMXPlus.List; + +/** + * Builds a path to the fixture with the given path components. + * + * e.g., makePathToFixture('basic.xml') + * + * @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)); +} + +/** + * A CompilerCallbacks implementation for testing + */ +class TestCompilerCallbacks implements CompilerCallbacks { + clear() { + this.messages = []; + } + messages: CompilerEvent[] = []; + loadFile(baseFilename: string, filename: string | URL): Buffer { + // TODO: translate filename based on the baseFilename + try { + return fs.readFileSync(filename); + } catch(e) { + if (e.code === 'ENOENT') { + return null; + } else { + throw e; + } + } + } + reportMessage(event: CompilerEvent): void { + // console.log(event.message); + this.messages.push(event); + } + loadLdmlKeyboardSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'ldml-keyboard.schema.json'), import.meta.url)); + } + loadKvksJsonSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'kvks.schema.json'), import.meta.url)); + } + loadKpjJsonSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'kpj.schema.json'), import.meta.url)); + } + loadLdmlKeyboardTestSchema(): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', 'ldml-keyboardtest.schema.json'), import.meta.url)); + } +}; + +export const compilerTestCallbacks = new TestCompilerCallbacks(); + +beforeEach(function() { + compilerTestCallbacks.clear(); +}); + +afterEach(function() { + if (this.currentTest.state !== 'passed') { + compilerTestCallbacks.messages.forEach(message => console.log(message.message)); + } +}); + + +export function loadSectionFixture(compilerClass: typeof SectionCompiler, filename: string, callbacks: TestCompilerCallbacks): Section { + callbacks.messages = []; + const inputFilename = makePathToFixture(filename); + const data = callbacks.loadFile(inputFilename, inputFilename); + assert.isNotNull(data); + + const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const source = reader.load(data); + assert.isNotNull(source); + assert.doesNotThrow(() => { + reader.validate(source, callbacks.loadLdmlKeyboardSchema()); + }); + + const compiler = new compilerClass(source, callbacks); + + if(!compiler.validate()) { + return null; + } + + let globalSections: GlobalSections = { + strs: new Strs(), + elem: null, + list: null, + }; + globalSections.elem = new Elem(globalSections.strs); + globalSections.list = new List(globalSections.strs); + + return compiler.compile(globalSections); +} + +export function loadTestdata(inputFilename: string, options: CompilerOptions) : LDMLKeyboardTestDataXMLSourceFile { + const k = new Compiler(compilerTestCallbacks, options); + const source = k.loadTestData(inputFilename); + return source; +} + +export function compileKeyboard(inputFilename: string, options: CompilerOptions): KMXPlusFile { + const k = new Compiler(compilerTestCallbacks, options); + const source = k.load(inputFilename); + checkMessages(); + assert.isNotNull(source, 'k.load should not have returned null'); + + const valid = k.validate(source); + checkMessages(); + assert.isTrue(valid, 'k.validate should not have failed'); + + const kmx = k.compile(source); + checkMessages(); + assert.isNotNull(kmx, 'k.compile should not have returned null'); + + // In order for the KMX file to be loaded by non-KMXPlus components, it is helpful + // to duplicate some of the metadata + KMXPlusMetadataCompiler.addKmxMetadata(kmx.kmxplus, kmx.keyboard, options); + + return kmx; +} + +export function compileVisualKeyboard(inputFilename: string, options: CompilerOptions): VisualKeyboard.VisualKeyboard { + const k = new Compiler(compilerTestCallbacks, options); + const source = k.load(inputFilename); + checkMessages(); + assert.isNotNull(source, 'k.load should not have returned null'); + + const valid = k.validate(source); + checkMessages(); + assert.isTrue(valid, 'k.validate should not have failed'); + + const vk = (new VisualKeyboardCompiler()).compile(source); + checkMessages(); + assert.isNotNull(vk, 'VisualKeyboardCompiler.compile should not have returned null'); + + return vk; +} + +export function checkMessages() { + if(compilerTestCallbacks.messages.length > 0) { + console.log(compilerTestCallbacks.messages); + } + assert.isEmpty(compilerTestCallbacks.messages); +} + +export interface CompilationCase { + /** + * path to xml, such as 'sections/layr/invalid-case.xml' + */ + subpath: string; + /** + * expected error messages. If falsy, expected to succeed. All must be present to pass. + */ + errors?: CompilerEvent[]; + /** + * expected warning messages. All must be present to pass. + */ + warnings?: CompilerEvent[]; + /** + * optional callback with the section + */ + callback?: (sect: KMXPlus.Section, subpath: string, callbacks: TestCompilerCallbacks ) => void; + /** + * if present, expect compiler to throw (use .* to match all) + */ + throws?: RegExp; +} + +/** + * Run a bunch of cases + * @param cases cases to run + * @param compiler argument to loadSectionFixture() + * @param callbacks argument to loadSectionFixture() + */ +export function testCompilationCases(compiler: typeof SectionCompiler, cases : CompilationCase[]) { + // we need our own callbacks rather than using the global so messages don't get mixed + const callbacks = new TestCompilerCallbacks(); + for (let testcase of cases) { + const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail + const testHeading = expectFailure ? `should fail to compile: ${testcase.subpath}`: + `should compile: ${testcase.subpath}`; + it(testHeading, function () { + callbacks.clear(); + // special case for an expected exception + if (testcase.throws) { + assert.throws(() => loadSectionFixture(compiler, testcase.subpath, callbacks), testcase.throws, 'expected exception from compilation'); + return; + } + let section = loadSectionFixture(compiler, testcase.subpath, callbacks); + if (expectFailure) { + assert.isNull(section, 'expected compilation result to be null, but got something'); + } else { + assert.isNotNull(section, `expected successful compilation, but got null and ${JSON.stringify(callbacks.messages)}`); + } + + // if we expected errors or warnings, show them + if (testcase.errors) { + assert.includeDeepMembers(callbacks.messages, testcase.errors, 'expected errors to be included'); + } + if (testcase.warnings) { + assert.includeDeepMembers(callbacks.messages, testcase.warnings, 'expected warnings to be included'); + } else if (!expectFailure) { + // no warnings, so expect zero messages + assert.strictEqual(callbacks.messages.length, 0, 'expected zero messages'); + } + + // run the user-supplied callback if any + if (testcase.callback) { + testcase.callback(section, testcase.subpath, callbacks); + } + }); + } +} diff --git a/developer/src/kmc-keyboard/test/test-bksp.ts b/developer/src/kmc-keyboard/test/test-bksp.ts new file mode 100644 index 00000000000..0254a4647f7 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-bksp.ts @@ -0,0 +1,26 @@ +import 'mocha'; +import { assert } from 'chai'; +import { BkspCompiler } from '../src/compiler/tran.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; +import { KMXPlus } from '@keymanapp/common-types'; + +import Bksp = KMXPlus.Bksp; +import BkspItemFlags = KMXPlus.BkspItemFlags; + +describe('bksp', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal bksp data', function() { + let bksp = loadSectionFixture(BkspCompiler, 'sections/bksp/minimal.xml', compilerTestCallbacks) as Bksp; + assert.lengthOf(compilerTestCallbacks.messages, 0); + + assert.lengthOf(bksp.items, 1); + assert.lengthOf(bksp.items[0].from, 2); + assert.strictEqual(bksp.items[0].from[0].value.value, "្"); + assert.strictEqual(bksp.items[0].from[1].value.value, "ម"); + assert.strictEqual(bksp.items[0].flags, BkspItemFlags.none); + assert.isEmpty(bksp.items[0].before); + assert.strictEqual(bksp.items[0].to.value, ""); + }); +}); + diff --git a/developer/src/kmc-keyboard/test/test-compiler-e2e.ts b/developer/src/kmc-keyboard/test/test-compiler-e2e.ts new file mode 100644 index 00000000000..ec711549051 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-compiler-e2e.ts @@ -0,0 +1,33 @@ +import 'mocha'; +import {assert} from 'chai'; +import x_hextobin from '@keymanapp/hextobin'; +import { KMXBuilder } from '@keymanapp/common-types'; +import {checkMessages, compileKeyboard, makePathToFixture} from './helpers/index.js'; + +const hextobin = (x_hextobin as any).default; + +describe('compiler-tests', function() { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should-build-fixtures', async function() { + // Let's build basic.xml + // It should match basic.kmx (built from basic.txt) + + const inputFilename = makePathToFixture('basic.xml'); + const binaryFilename = makePathToFixture('basic.txt'); + + // Compile the keyboard + const kmx = compileKeyboard(inputFilename, {debug: true, addCompilerVersion: false}); + assert.isNotNull(kmx); + + // Use the builder to generate the binary output file + const builder = new KMXBuilder(kmx, true); + const code = builder.compile(); + checkMessages(); + assert.isNotNull(code); + + // Compare output + let expected = await hextobin(binaryFilename, undefined, {silent:true}); + assert.deepEqual(code, expected); + }); +}); \ No newline at end of file diff --git a/developer/src/kmc-keyboard/test/test-disp.ts b/developer/src/kmc-keyboard/test/test-disp.ts new file mode 100644 index 00000000000..5f387658594 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-disp.ts @@ -0,0 +1,66 @@ +import 'mocha'; +import {assert} from 'chai'; +import { DispCompiler } from '../src/compiler/disp.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; +import { KMXPlus } from '@keymanapp/common-types'; +import { CompilerMessages } from '../src/compiler/messages.js'; + +import Disp = KMXPlus.Disp; + +describe('disp', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal disp', function() { + let disp = loadSectionFixture(DispCompiler, 'sections/disp/minimal.xml', compilerTestCallbacks) as Disp; + assert.equal(compilerTestCallbacks.messages.length, 0); + + assert.ok(disp?.disps); + assert.equal(disp.disps.length, 0); + }); + it('should compile typical disp', function() { + let disp = loadSectionFixture(DispCompiler, 'sections/disp/typical.xml', compilerTestCallbacks) as Disp; + assert.equal(compilerTestCallbacks.messages.length, 0); + + assert.ok(disp?.disps); + assert.equal(disp.disps.length, 1); + assert.equal(disp.disps[0].to?.value, '\u0300'); + assert.equal(disp.disps[0].display?.value, '`'); + }); + it('should compile maximal disp', function() { + let disp = loadSectionFixture(DispCompiler, 'sections/disp/maximal.xml', compilerTestCallbacks) as Disp; + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(disp?.baseCharacter?.value, 'x'); + assert.ok(disp?.disps); + assert.equal(disp.disps.length, 2); + assert.equal(disp.disps[0].to?.value, 'e'); + assert.equal(disp.disps[0].display?.value, '(e)'); + assert.equal(disp.disps[1].to?.value, 'f'); + assert.equal(disp.disps[1].display?.value, '(f)'); + }); + it('should compile escaped disp', function() { + let disp = loadSectionFixture(DispCompiler, 'sections/disp/escaped.xml', compilerTestCallbacks) as Disp; + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(disp?.baseCharacter?.value, 'x'); + assert.ok(disp?.disps); + assert.equal(disp.disps.length, 2); + assert.equal(disp.disps[0].to?.value, 'e'); + assert.equal(disp.disps[0].display?.value, '(e)'); + assert.equal(disp.disps[1].to?.value, 'f'); + assert.equal(disp.disps[1].display?.value, '(f)'); + }); + it('should compile options-only disp', function() { + let disp = loadSectionFixture(DispCompiler, 'sections/disp/maximal.xml', compilerTestCallbacks) as Disp; + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(disp?.baseCharacter?.value, 'x'); + assert.ok(disp?.disps); + }); + + it('should reject duplicate tos', function() { + let disp = loadSectionFixture(DispCompiler, 'sections/disp/invalid-dupto.xml', compilerTestCallbacks) as Disp; + assert.isNull(disp); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_DisplayIsRepeated({to:'e'})); + }); + +}); + diff --git a/developer/src/kmc-keyboard/test/test-finl.ts b/developer/src/kmc-keyboard/test/test-finl.ts new file mode 100644 index 00000000000..2aa8495bb47 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-finl.ts @@ -0,0 +1,26 @@ +import 'mocha'; +import { assert } from 'chai'; +import { FinlCompiler } from '../src/compiler/tran.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; +import { KMXPlus } from '@keymanapp/common-types'; + +import Finl = KMXPlus.Finl; +import FinlItemFlags = KMXPlus.FinlItemFlags; + +describe('finl', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal finl data', function() { + let finl = loadSectionFixture(FinlCompiler, 'sections/finl/minimal.xml', compilerTestCallbacks) as Finl; + assert.lengthOf(compilerTestCallbacks.messages, 0); + + assert.lengthOf(finl.items, 1); + assert.lengthOf(finl.items[0].from, 2); + assert.strictEqual(finl.items[0].from[0].value.value, "x"); + assert.strictEqual(finl.items[0].from[1].value.value, "x"); + assert.strictEqual(finl.items[0].flags, FinlItemFlags.error); + assert.isEmpty(finl.items[0].before); + assert.strictEqual(finl.items[0].to.value, "x"); + }); +}); + diff --git a/developer/src/kmc-keyboard/test/test-keymanweb-compiler.ts b/developer/src/kmc-keyboard/test/test-keymanweb-compiler.ts new file mode 100644 index 00000000000..c4449b44c14 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-keymanweb-compiler.ts @@ -0,0 +1,49 @@ +import 'mocha'; +import { assert } from 'chai'; +import { checkMessages, compilerTestCallbacks, makePathToFixture } from './helpers/index.js'; +import { KeymanWebCompiler } from '../src/compiler/keymanweb-compiler.js'; +import Compiler from '../src/compiler/compiler.js'; +import * as fs from 'fs'; + +describe('KeymanWebCompiler', function() { + + it('should build a .js file', async function() { + // Let's build basic.xml + // It should generate content identical to basic.js + const inputFilename = makePathToFixture('basic.xml'); + const outputFilename = makePathToFixture('basic.js'); + const outputFilenameNoDebug = makePathToFixture('basic-no-debug.js'); + + // Load input data; we'll use the LDML keyboard compiler loader to save us + // effort here + const k = new Compiler(compilerTestCallbacks, {debug: true, addCompilerVersion: false}); + const source = k.load(inputFilename); + checkMessages(); + assert.isNotNull(source, 'k.load should not have returned null'); + + // Sanity check ... this is also checked in other tests + const valid = k.validate(source); + checkMessages(); + assert.isTrue(valid, 'k.validate should not have failed'); + + // Actual test: compile to javascript + const jsCompiler = new KeymanWebCompiler({debug: true}); + const output = jsCompiler.compile('basic.xml', source); + assert.isNotNull(output); + + // Does the emitted js match? + const outputFixture = fs.readFileSync(outputFilename, 'utf-8').replaceAll(/\r\n/g, '\n'); + assert.strictEqual(output, outputFixture); + + // Second test: compile to javascript without debug formatting + const jsCompilerNoDebug = new KeymanWebCompiler({debug: false}); + const outputNoDebug = jsCompilerNoDebug.compile('basic.xml', source); + assert.isNotNull(outputNoDebug); + + // Does the emitted js match? + const outputFixtureNoDebug = fs.readFileSync(outputFilenameNoDebug, 'utf-8').replaceAll(/\r\n/g, '\n'); + assert.strictEqual(outputNoDebug, outputFixtureNoDebug); + + // TODO(lowpri): consider using Typescript parser to generate AST for further validation + }); +}); \ No newline at end of file diff --git a/developer/src/kmc-keyboard/test/test-keys.ts b/developer/src/kmc-keyboard/test/test-keys.ts new file mode 100644 index 00000000000..b584d7eb4cd --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-keys.ts @@ -0,0 +1,258 @@ +import 'mocha'; +import { assert } from 'chai'; +import { KeysCompiler } from '../src/compiler/keys.js'; +import { compilerTestCallbacks, loadSectionFixture, testCompilationCases } from './helpers/index.js'; +import { KMXPlus, Constants } from '@keymanapp/common-types'; +import { CompilerMessages } from '../src/compiler/messages.js'; +import { constants } from '@keymanapp/ldml-keyboard-constants'; +import Keys = KMXPlus.Keys; +const K = Constants.USVirtualKeyCodes; + +describe('keys', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal keys data', function () { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/minimal.xml', compilerTestCallbacks) as Keys; + assert.ok(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 1); + assert.equal(keys.flicks.length, 1); // there's always a 'null' flick + assert.equal(keys.keys[0].to.value, '🪦'); + assert.equal(keys.keys[0].id.value, 'grave'); + }); + + it('should compile maximal keys data', function () { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/maximal.xml', compilerTestCallbacks) as Keys; + assert.ok(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 4); + + const [q] = keys.keys.filter(({ id }) => id.value === 'q'); + assert.ok(q); + assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); + assert.equal(q.width, 32, 'q\'s width'); // ceil(3.14159 * 10.0) + assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem + assert.equal(q.longPress.toString(), 'á é í'); + assert.equal(q.longPressDefault.value, 'é'); + assert.equal(q.multiTap.toString(), 'ä ë ï'); + + const [flick0] = keys.flicks.filter(({ id }) => id.value === 'flick0'); + assert.ok(flick0); + assert.equal(flick0.flicks.length, 2); + + const [flick0_nw_se] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('nw se'.split(' '))); + assert.ok(flick0_nw_se); + assert.equal(flick0_nw_se.to?.value, 'ç'); + + 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, 'ê'); + }); + + it('should compile escaped keys data', function () { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/escaped.xml', compilerTestCallbacks) as Keys; + assert.ok(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 4); + + const [q] = keys.keys.filter(({ id }) => id.value === 'q'); + assert.ok(q); + assert.isFalse(!!(q.flags & constants.keys_key_flags_gap)); + assert.equal(q.width, 32); // ceil(3.1 * 10) + assert.equal(q.flicks, 'flick0'); // note this is a string, not a StrsItem + assert.equal(q.longPress.toString(), 'á é í'); + assert.equal(q.longPressDefault.value, 'é'); + assert.equal(q.multiTap.toString(), 'ä ë ï'); + + const [flick0] = keys.flicks.filter(({ id }) => id.value === 'flick0'); + assert.ok(flick0); + assert.equal(flick0.flicks.length, 2); + + const [flick0_nw_se] = flick0.flicks.filter(({ directions }) => directions && directions.isEqual('nw se'.split(' '))); + assert.ok(flick0_nw_se); + assert.equal(flick0_nw_se.to?.value, 'ç'); + + 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, 'ế'); + }); + + + it('should accept layouts with gap/switch keys', function () { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/gap-switch.xml', compilerTestCallbacks) as Keys; + assert.ok(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 4); + + const [Qgap] = keys.keys.filter(({ id }) => id.value === 'Q'); + assert.ok(Qgap); + assert.isTrue(!!(Qgap.flags & constants.keys_key_flags_gap), 'Q’s gap='); + + const [Wshift] = keys.keys.filter(({ id }) => id.value === 'W'); + assert.isNotNull(Wshift); + assert.isFalse(!!(Wshift.flags & constants.keys_key_flags_gap)); + assert.equal(Wshift.switch.value, 'shift'); + + }); + + testCompilationCases(KeysCompiler, [ + { + subpath: 'sections/keys/escaped2.xml', + callback: (keys, subpath, callbacks) => { + assert.isNotNull(keys); + assert.equal((keys).keys.length, 1); + const [q] = (keys).keys.filter(({ id }) => id.value === 'grave'); + assert.equal(q.to.value, String.fromCodePoint(0x1faa6)); + }, + }, + ]); +}); + +describe('keys.kmap', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal kmap data', function() { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/minimal.xml', compilerTestCallbacks) as Keys; + assert.isNotNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.kmap.length, 1); + }); + + testCompilationCases(KeysCompiler, [ + { + subpath: 'sections/keys/hardware.xml', + callback: (sect, subpath, callbacks) => { + const keys = sect as Keys; + assert.isNotNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 4); + assert.sameDeepMembers(keys.kmap, [ + { + vkey: K.K_BKQUOTE, + key: 'qqq', + mod: constants.keys_mod_none, + }, + { + vkey: K.K_1, + key: 'www', + mod: constants.keys_mod_none, + }, + { + vkey: K.K_BKQUOTE, + key: 'QQQ', + mod: constants.keys_mod_shift, + }, + { + vkey: K.K_1, + key: 'WWW', + mod: constants.keys_mod_shift, + }, + ]); + }, + }, + { + subpath: 'sections/keys/hardware_us.xml', + callback: (sect, subpath, callbacks) => { + const keys = sect as Keys; + assert.isNotNull(keys); + assert.includeDeepMembers(keys.kmap, [ + { + vkey: K.K_BKSLASH, + key: 'backslash', + mod: constants.keys_mod_none, + }, + { + vkey: K.K_Z, + key: 'z', + mod: constants.keys_mod_none, + }, + { + vkey: K.K_BKQUOTE, + key: 'grave', + mod: constants.keys_mod_none, + }, + ]); + }, + }, + { + subpath: 'sections/keys/hardware_iso.xml', + callback: (sect, subpath, callbacks) => { + const keys = sect as Keys; + assert.isNotNull(keys); + assert.includeDeepMembers(keys.kmap, [ + { + vkey: K.K_oE2, + key: 'backslash', + mod: constants.keys_mod_none, + }, + { + vkey: 'Z'.charCodeAt(0), + key: 'z', + mod: constants.keys_mod_none, + }, + { + vkey: 192, + key: 'grave', + mod: constants.keys_mod_none, + }, + ]); + }, + }, + { + subpath: 'sections/keys/invalid-bad-modifier.xml', + errors: [ + CompilerMessages.Error_InvalidModifier({layer:'base',modifier:'altR-shift'}), + ] + }, + { + subpath: 'sections/keys/invalid-missing-flick.xml', + errors: [ + CompilerMessages.Error_MissingFlicks({flicks:'an-undefined-flick-id',id:'Q'}), + ] + }, + ]); + + it('should reject structurally invalid layers', function() { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-missing-layer.xml', compilerTestCallbacks) as Keys; + assert.isNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 1); + + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_MustBeAtLeastOneLayerElement()); + }); + + it('should reject layouts with too many hardware rows', function() { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-rows.xml', compilerTestCallbacks) as Keys; + assert.isNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 1); + + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_HardwareLayerHasTooManyRows()); + }); + + it('should reject layouts with too many hardware keys', function() { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-hardware-too-many-keys.xml', compilerTestCallbacks) as Keys; + assert.isNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 1); + + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_RowOnHardwareLayerHasTooManyKeys({row: 1, hardware: 'us'})); + }); + + it('should reject layouts with undefined keys', function() { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-undefined-key.xml', compilerTestCallbacks) as Keys; + assert.isNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 1); + + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_KeyNotFoundInKeyBag({col: 1, form: 'hardware', keyId: 'foo', layer: 'base', row: 1})); + }); + it('should reject layouts with invalid keys', function() { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/invalid-key-missing-attrs.xml', compilerTestCallbacks) as Keys; + assert.isNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_KeyMissingToGapOrSwitch({keyId: 'Q'})); + }); + it('should accept layouts with gap/switch keys', function() { + let keys = loadSectionFixture(KeysCompiler, 'sections/keys/gap-switch.xml', compilerTestCallbacks) as Keys; + assert.isNotNull(keys); + assert.equal(compilerTestCallbacks.messages.length, 0); + assert.equal(keys.keys.length, 4); + }); +}); diff --git a/developer/src/kmc-keyboard/test/test-layr.ts b/developer/src/kmc-keyboard/test/test-layr.ts new file mode 100644 index 00000000000..d30d71e2a79 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-layr.ts @@ -0,0 +1,120 @@ +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 { KMXPlus, CommonTypesMessages } from '@keymanapp/common-types'; +import { constants } from '@keymanapp/ldml-keyboard-constants'; + +import Layr = KMXPlus.Layr; +import LayrRow = KMXPlus.LayrRow; + +function allKeysOk(row : LayrRow, str : string, msg? : string) { + const split = str.split(' '); + assert.equal(row.keys.length, split.length, msg); + for (let i=0; i v.hardware === constants.layr_list_hardware_abnt2); + assert.ok(listHardware); + assert.equal(listHardware.minDeviceWidth, 0); + assert.equal(listHardware.layers.length, 2); + const hardware0 = listHardware.layers[0]; + assert.ok(hardware0); + assert.equal(hardware0.id.value, 'base'); + assert.equal(hardware0.mod, constants.keys_mod_none); + const hardware0row0 = hardware0.rows[0]; + assert.ok(hardware0row0); + assert.equal(hardware0row0.keys.length, 2); + allKeysOk(hardware0row0,'Q W', 'hardware0row0'); + const hardware1 = listHardware.layers[1]; + assert.ok(hardware1); + assert.equal(hardware1.rows.length, 1); + assert.equal(hardware1.id.value, 'shift'); + assert.equal(hardware1.mod, constants.keys_mod_shift); + const hardware1row0 = hardware1.rows[0]; + assert.ok(hardware1row0); + assert.equal(hardware1row0.keys.length, 2); + allKeysOk(hardware1row0,'q w', 'hardware1row0'); + + const listTouch = layr.lists.find(v => v.hardware === constants.layr_list_hardware_touch); + assert.ok(listTouch); + assert.equal(listTouch.minDeviceWidth, 300); + assert.equal(listTouch.layers.length, 1); + const touch0 = listTouch.layers[0]; + assert.ok(touch0); + assert.equal(touch0.rows.length, 1); + assert.equal(touch0.id.value, 'base'); + assert.equal(touch0.mod, constants.keys_mod_none); + const touch0row0 = touch0.rows[0]; + assert.ok(touch0row0); + assert.equal(touch0row0.keys.length, 4); + allKeysOk(touch0row0,'Q q W w', 'touch0row0'); + }); + testCompilationCases(LayrCompiler, [ + { + subpath: 'sections/layr/invalid-invalid-hardware.xml', + errors: [CompilerMessages.Error_InvalidHardware({hardware: 'stenography'})], + }, + { + subpath: 'sections/layr/invalid-missing-hardware.xml', + errors: [CompilerMessages.Error_MissingHardware()], + }, + { + subpath: 'sections/layr/invalid-multi-hardware.xml', + errors: [CompilerMessages.Error_MustHaveAtMostOneLayersElementPerForm({ form: 'hardware' })], + }, + { + subpath: 'sections/layr/invalid-touch-hardware.xml', + errors: [CompilerMessages.Error_NoHardwareOnTouch({ hardware: 'iso' })], + }, + { + subpath: 'sections/layr/invalid-invalid-form.xml', + errors: [CommonTypesMessages.Error_SchemaValidationError({ + instancePath: '/keyboard/layers/0/form', + keyword: 'enum', + message: 'must be equal to one of the allowed values', + params: `allowedValues="hardware,touch"`}),], + }, + { + subpath: 'sections/layr/invalid-missing-layer.xml', + errors: [CompilerMessages.Error_MustBeAtLeastOneLayerElement()], + }, + ]); +}); diff --git a/developer/src/kmc-keyboard/test/test-loca.ts b/developer/src/kmc-keyboard/test/test-loca.ts new file mode 100644 index 00000000000..317902316c1 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-loca.ts @@ -0,0 +1,51 @@ +import 'mocha'; +import { assert } from 'chai'; +import { LocaCompiler } from '../src/compiler/loca.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; +import { KMXPlus } from '@keymanapp/common-types'; +import { CompilerMessages } from '../src/compiler/messages.js'; + +import Loca = KMXPlus.Loca; + +describe('loca', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal loca data', function() { + let loca = loadSectionFixture(LocaCompiler, 'sections/loca/minimal.xml', compilerTestCallbacks) as Loca; + assert.isObject(loca); + + assert.equal(compilerTestCallbacks.messages.length, 0); + + assert.equal(loca.locales.length, 1); + assert.equal(loca.locales[0].value.toLowerCase(), 'mt'); + }); + + it('should compile multiple locales', function() { + let loca = loadSectionFixture(LocaCompiler, 'sections/loca/multiple.xml', compilerTestCallbacks) as Loca; + assert.isObject(loca); + + // Note: multiple.xml includes fr-FR twice, with differing case, which should be canonicalized + assert.equal(compilerTestCallbacks.messages.length, 4); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-FR', locale: 'fr'})); + assert.deepEqual(compilerTestCallbacks.messages[1], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'km-khmr-kh', locale: 'km'})); + assert.deepEqual(compilerTestCallbacks.messages[2], CompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-fr', locale: 'fr'})); + assert.deepEqual(compilerTestCallbacks.messages[3], CompilerMessages.Hint_OneOrMoreRepeatedLocales()); + + // Original is 6 locales, now five minimized in the results + assert.equal(loca.locales.length, 5); + assert.equal(loca.locales[0].value, 'mt'); + assert.equal(loca.locales[1].value, 'fr'); // Original fr-FR + assert.equal(loca.locales[2].value, 'km'); // Original km-Khmr-kh + assert.equal(loca.locales[3].value, 'qq-Abcd-ZZ-x-foobar'); + assert.equal(loca.locales[4].value, 'en-fonipa'); + }); + + it('should reject structurally invalid locales', function() { + let loca = loadSectionFixture(LocaCompiler, 'sections/loca/invalid-locale.xml', compilerTestCallbacks) as Loca; + assert.isNull(loca); + assert.equal(compilerTestCallbacks.messages.length, 1); + // We'll only test one invalid BCP 47 tag to verify that we are properly calling BCP 47 validation routines. + // Furthermore, we are testing BCP 47 structure, not the validity of each subtag -- we must assume the author knows of new subtags! + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidLocale({tag:'en-*'})); + }) +}); diff --git a/developer/src/kmc-keyboard/test/test-messages.ts b/developer/src/kmc-keyboard/test/test-messages.ts new file mode 100644 index 00000000000..e9d897b5491 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-messages.ts @@ -0,0 +1,75 @@ +import 'mocha'; +import {assert, expect} from 'chai'; +import { CompilerMessages } from '../src/compiler/messages.js'; +import { CompilerErrorSeverity } from '@keymanapp/common-types'; + +const toTitleCase = (s: string) => s.substring(0, 1).toUpperCase() + s.substring(1).toLowerCase(); + +// +// The purpose of these tests is to ensure that our slightly WET constant +// definitions for compiler errors are consistent in their usage: +// +// * that names are consistent between the message functions and constants +// * that constants have unique codes +// * that constants have the right error mask for their type +// +// This means we don't have to worry about DRYing out the constants, which would +// probably require us to have a preprocessing step, which has its own negatives. +// + +describe('compiler messages', function () { + it('each compiler message function has a corresponding constant', function() { + let keys = Object.keys(CompilerMessages); + + const m = CompilerMessages as Record; + + for(let key of keys) { + if(key == 'severityName') { + // any helper functions we skip here + continue; + } + + if(typeof m[key] == 'function') { + const o = /^(Info|Hint|Warning|Error|Fatal)_([A-Za-z0-9_]+)$/.exec(key); + expect(o).to.be.instanceOf(Array); + + const c = o[1].toUpperCase() + '_' + o[2]; + expect(m[c]).to.be.a('number', `Expected constant name ${c} to exist`); + + let v = m[key]('','','','','','','','','','','','' /* ignore arguments*/); + expect(v.code).to.equal(m[c], `Function ${key} returns the wrong code`); + } + else if(typeof m[key] == 'number') { + const o = /^(INFO|HINT|WARNING|ERROR|FATAL)_([A-Za-z0-9_]+)$/.exec(key); + expect(o).to.be.instanceOf(Array); + + const f = toTitleCase(o[1]) + '_' + o[2]; + expect(m[f]).to.be.a('function', `Expected function name ${f} to exist`); + } else { + assert.fail(`Unexepected compiler message member ${key}`); + } + } + }); + + it('each compiler message constant to be have the right mask and be unique', function() { + let keys = Object.keys(CompilerMessages); + + const m = CompilerMessages as Record; + + let codes: number[] = []; + + for(let key of keys) { + if(typeof m[key] == 'number') { + const o = /^(INFO|HINT|WARNING|ERROR|FATAL)_([A-Za-z0-9_]+)$/.exec(key); + expect(o).to.be.instanceOf(Array); + + const mask = CompilerMessages.severityName(m[key]); + expect(o[1]).to.equal(mask, `Mask value for ${key} does not match`); + + const code = m[key] & CompilerErrorSeverity.Error_Mask; + expect(codes).to.not.contain(code, `Constant value ${key} is not unique`); + codes.push(code); + } + } + }); +}); diff --git a/developer/src/kmc-keyboard/test/test-meta.ts b/developer/src/kmc-keyboard/test/test-meta.ts new file mode 100644 index 00000000000..c2e32d68b4d --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-meta.ts @@ -0,0 +1,58 @@ +import 'mocha'; +import {assert} from 'chai'; +import { MetaCompiler } from '../src/compiler/meta.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; +import { KMXPlus } from '@keymanapp/common-types'; +import { CompilerMessages } from '../src/compiler/messages.js'; + +import KeyboardSettings = KMXPlus.KeyboardSettings; +import Meta = KMXPlus.Meta; + +describe('meta', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal metadata', function() { + let meta = loadSectionFixture(MetaCompiler, 'sections/meta/minimal.xml', compilerTestCallbacks) as Meta; + assert.equal(compilerTestCallbacks.messages.length, 0); + + assert.isEmpty(meta.author.value); // TODO-LDML: default author string "unknown"? + assert.equal(meta.conform.value, 'techpreview'); + assert.isEmpty(meta.layout.value); // TODO-LDML: assumed layout? + assert.isEmpty(meta.normalization.value); // TODO-LDML: assumed normalization? + assert.isEmpty(meta.indicator.value); // TODO-LDML: synthesize an indicator? + assert.equal(meta.settings, KeyboardSettings.none); + }); + + it('should compile maximal metadata', function() { + let meta = loadSectionFixture(MetaCompiler, 'sections/meta/maximal.xml', compilerTestCallbacks) as Meta; + assert.equal(compilerTestCallbacks.messages.length, 0); + + assert.equal(meta.author.value, 'The Keyman Team'); + assert.equal(meta.conform.value, 'techpreview'); + assert.equal(meta.layout.value, 'QWIRKY'); + assert.equal(meta.normalization.value, 'NFC'); + assert.equal(meta.indicator.value, 'QW'); + assert.equal(meta.version.value, "1.2.3"); + assert.equal(meta.settings, KeyboardSettings.fallback | KeyboardSettings.transformFailure | KeyboardSettings.transformPartial); + }); + + it('should reject invalid normalization', function() { + let meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-normalization.xml', compilerTestCallbacks) as Meta; + assert.isNull(meta); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidNormalization({form:'NFQ'})); + }); + + it('should reject invalid version', function() { + let meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-1.0.xml', compilerTestCallbacks) as Meta; + assert.isNull(meta); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidVersion({version:'1.0'})); + + meta = loadSectionFixture(MetaCompiler, 'sections/meta/invalid-version-v1.0.3.xml', compilerTestCallbacks) as Meta; + assert.isNull(meta); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_InvalidVersion({version:'v1.0.3'})); + }); +}); + diff --git a/developer/src/kmc-keyboard/test/test-metadata-compiler.ts b/developer/src/kmc-keyboard/test/test-metadata-compiler.ts new file mode 100644 index 00000000000..da630388345 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-metadata-compiler.ts @@ -0,0 +1,74 @@ +import 'mocha'; +import { assert } from 'chai'; +import { checkMessages, compileKeyboard, makePathToFixture } from './helpers/index.js'; +import { KMX } from '@keymanapp/common-types'; +import KEYMAN_VERSION from '@keymanapp/keyman-version/keyman-version.mjs'; + +import KMXFile = KMX.KMXFile; + +describe('kmx metadata compiler', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile metadata with debug and compiler version', function() { + const inputFilename = makePathToFixture('basic.xml'); + + // Compile the keyboard + const kmx = compileKeyboard(inputFilename, {debug:true, addCompilerVersion:true}); + checkMessages(); + assert.isNotNull(kmx); + + // Order of stores is not significant in kmx spec, but kmxplus compiler will + // always store according to dwSystemID binary order for non-zero + // dwSystemID, then by dpName binary order, and finally by dpString binary + // order + + // TSS_NAME = 7 + // TSS_COMPILEDVERSION = 20 + // TSS_KEYBOARDVERSION = 36 + // TSS_TARGETS = 38 + + assert.equal(kmx.keyboard.stores[0].dpName, '&NAME'); + assert.equal(kmx.keyboard.stores[0].dpString, 'TestKbd'); + assert.equal(kmx.keyboard.stores[0].dwSystemID, KMXFile.TSS_NAME); + + assert.equal(kmx.keyboard.stores[1].dpName, ''); + assert.equal(kmx.keyboard.stores[1].dpString, KEYMAN_VERSION.VERSION_WITH_TAG); + assert.equal(kmx.keyboard.stores[1].dwSystemID, KMXFile.TSS_COMPILEDVERSION); + + assert.equal(kmx.keyboard.stores[2].dpName, '&KEYBOARDVERSION'); + assert.equal(kmx.keyboard.stores[2].dpString, '1.0.0'); + assert.equal(kmx.keyboard.stores[2].dwSystemID, KMXFile.TSS_KEYBOARDVERSION); + + assert.equal(kmx.keyboard.stores[3].dpName, '&TARGETS'); + assert.equal(kmx.keyboard.stores[3].dpString, 'desktop'); + assert.equal(kmx.keyboard.stores[3].dwSystemID, KMXFile.TSS_TARGETS); + }); + + it('should compile metadata with no compiler version', function() { + const inputFilename = makePathToFixture('basic.xml'); + + // Compile the keyboard + const kmx = compileKeyboard(inputFilename, {debug:true, addCompilerVersion:false}); + checkMessages(); + assert.isNotNull(kmx); + + // TSS_NAME = 7 + // -- TSS_COMPILEDVERSION = 20 -- not included with addCompilerVersion = false + // TSS_KEYBOARDVERSION = 36 + // TSS_TARGETS = 38 + + assert.equal(kmx.keyboard.stores[0].dpName, '&NAME'); + assert.equal(kmx.keyboard.stores[0].dpString, 'TestKbd'); + assert.equal(kmx.keyboard.stores[0].dwSystemID, KMXFile.TSS_NAME); + + assert.equal(kmx.keyboard.stores[1].dpName, '&KEYBOARDVERSION'); + assert.equal(kmx.keyboard.stores[1].dpString, '1.0.0'); + assert.equal(kmx.keyboard.stores[1].dwSystemID, KMXFile.TSS_KEYBOARDVERSION); + + assert.equal(kmx.keyboard.stores[2].dpName, '&TARGETS'); + assert.equal(kmx.keyboard.stores[2].dpString, 'desktop'); + assert.equal(kmx.keyboard.stores[2].dwSystemID, KMXFile.TSS_TARGETS); + }); + +}); + diff --git a/developer/src/kmc-keyboard/test/test-name.ts b/developer/src/kmc-keyboard/test/test-name.ts new file mode 100644 index 00000000000..d206c8a4868 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-name.ts @@ -0,0 +1,34 @@ +import 'mocha'; +import { assert } from 'chai'; +import { NameCompiler } from '../src/compiler/name.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; +import { KMXPlus } from '@keymanapp/common-types'; + +import Name = KMXPlus.Name; + +describe('name', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal name data', function() { + let name = loadSectionFixture(NameCompiler, 'sections/name/minimal.xml', compilerTestCallbacks) as Name; + assert.equal(compilerTestCallbacks.messages.length, 0); + + assert.equal(name.names.length, 1); + assert.equal(name.names[0].value, 'My First Keyboard'); + }); + + it('should compile multiple names', function() { + let name = loadSectionFixture(NameCompiler, 'sections/name/multiple.xml', compilerTestCallbacks) as Name; + assert.equal(compilerTestCallbacks.messages.length, 0); + + assert.equal(name.names.length, 5); + assert.equal(name.names[0].value, 'My Second Keyboard'); + assert.equal(name.names[1].value, 'My 2nd Keyboard'); + assert.equal(name.names[2].value, 'win:kbd2'); + assert.equal(name.names[3].value, 'mac:keybd_2'); + assert.equal(name.names[4].value, 'web:keyboard-2'); + }); + + //TODO-LDML: should we be linting on repeated names? +}); + diff --git a/developer/src/kmc-keyboard/test/test-ordr.ts b/developer/src/kmc-keyboard/test/test-ordr.ts new file mode 100644 index 00000000000..d39208ba926 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-ordr.ts @@ -0,0 +1,29 @@ +import 'mocha'; +import { assert } from 'chai'; +import { OrdrCompiler } from '../src/compiler/ordr.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; +import { KMXPlus } from '@keymanapp/common-types'; + +import Ordr = KMXPlus.Ordr; + +describe('ordr', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal ordr data', function() { + let ordr = loadSectionFixture(OrdrCompiler, 'sections/ordr/minimal.xml', compilerTestCallbacks) as Ordr; + assert.lengthOf(compilerTestCallbacks.messages, 0); + + assert.lengthOf(ordr.items, 1); + assert.lengthOf(ordr.items[0].elements, 4); + assert.strictEqual(ordr.items[0].elements[0].value.value, "ខ"); + assert.strictEqual(ordr.items[0].elements[1].value.value, "ែ"); + assert.strictEqual(ordr.items[0].elements[2].value.value, "្"); + assert.strictEqual(ordr.items[0].elements[3].value.value, "ម"); + assert.strictEqual(ordr.items[0].elements[0].order, 1); + assert.strictEqual(ordr.items[0].elements[1].order, 3); + assert.strictEqual(ordr.items[0].elements[2].order, 4); + assert.strictEqual(ordr.items[0].elements[3].order, 2); + assert.isEmpty(ordr.items[0].before); + }); +}); + diff --git a/developer/src/kmc-keyboard/test/test-testdata-e2e.ts b/developer/src/kmc-keyboard/test/test-testdata-e2e.ts new file mode 100644 index 00000000000..25ac1963213 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-testdata-e2e.ts @@ -0,0 +1,24 @@ +import { readFileSync } from 'fs'; +import 'mocha'; +import {assert} from 'chai'; +import {loadTestdata, makePathToFixture} from './helpers/index.js'; + +describe('testdata-tests', function() { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should-build-testdata-fixtures', async function() { + // Let's build test-fr.json + // It should match test-fr.json (built from test-fr.xml) + + const inputFilename = makePathToFixture('test-fr.xml'); + const jsonFilename = makePathToFixture('test-fr.json'); + + // Compile the keyboard + const testData = loadTestdata(inputFilename, {debug: true, addCompilerVersion: false}); + assert.isNotNull(testData); + + const jsonData = JSON.parse(readFileSync(jsonFilename, 'utf-8')); + + assert.deepEqual(testData, jsonData); + }); +}); diff --git a/developer/src/kmc-keyboard/test/test-tran.ts b/developer/src/kmc-keyboard/test/test-tran.ts new file mode 100644 index 00000000000..9a75defe2ea --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-tran.ts @@ -0,0 +1,26 @@ +import 'mocha'; +import { assert } from 'chai'; +import { TranCompiler } from '../src/compiler/tran.js'; +import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js'; +import { KMXPlus } from '@keymanapp/common-types'; + +import Tran = KMXPlus.Tran; +import TranItemFlags = KMXPlus.TranItemFlags; + +describe('tran', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it('should compile minimal tran data', function() { + let tran = loadSectionFixture(TranCompiler, 'sections/tran/minimal.xml', compilerTestCallbacks) as Tran; + assert.lengthOf(compilerTestCallbacks.messages, 0); + + assert.lengthOf(tran.items, 1); + assert.lengthOf(tran.items[0].from, 2); + assert.strictEqual(tran.items[0].from[0].value.value, "x"); + assert.strictEqual(tran.items[0].from[1].value.value, "x"); + assert.strictEqual(tran.items[0].flags, TranItemFlags.error); + assert.isEmpty(tran.items[0].before); + assert.strictEqual(tran.items[0].to.value, "x"); + }); +}); + diff --git a/developer/src/kmc-keyboard/test/test-utils.ts b/developer/src/kmc-keyboard/test/test-utils.ts new file mode 100644 index 00000000000..de41caa64b3 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-utils.ts @@ -0,0 +1,164 @@ +import 'mocha'; +import {assert} from 'chai'; +import { isValidEnumValue, calculateUniqueKeys, allUsedKeyIdsInLayers, translateLayerAttrToModifier, validModifier } from '../src/util/util.js'; +import { constants } from "@keymanapp/ldml-keyboard-constants"; +import { LDMLKeyboard } from '@keymanapp/common-types'; + +describe('test of util/util.ts', () => { + describe('isValidEnumValue()', () => { + enum MyFruit { + apple, peach, pear, plum + }; + enum MyNumbers { + wieħed=1, tnejn=2, tlieta=3 + }; + enum MyLetters { + akka='h', magħtugha='ħ' + }; + + it('should handle a bare enum', ()=> { + assert.ok(isValidEnumValue(MyFruit, 'apple')); + assert.notOk(isValidEnumValue(MyFruit, 'banana')); + assert.notOk(isValidEnumValue(MyFruit, '')); + assert.notOk(isValidEnumValue(MyFruit, undefined)); + assert.notOk(isValidEnumValue(MyFruit, null)); + assert.notOk(isValidEnumValue(MyFruit, 'unknown')); + }); + + it('should handle a numerical enum', ()=> { + assert.ok(isValidEnumValue(MyNumbers, 'tlieta')); + assert.notOk(isValidEnumValue(MyNumbers, '3')); + assert.notOk(isValidEnumValue(MyNumbers, 'seventy-six')); + assert.notOk(isValidEnumValue(MyNumbers, '76')); + }); + + it('should handle a string enum', ()=> { + assert.ok(isValidEnumValue(MyLetters, 'h')); + assert.ok(isValidEnumValue(MyLetters, 'ħ')); + assert.notOk(isValidEnumValue(MyLetters, 'akka')); + assert.notOk(isValidEnumValue(MyLetters, 'z')); + assert.notOk(isValidEnumValue(MyLetters, 'zed')); + }); + }); + + describe('calculateUniqueKeys()', () => { + it('should handle some null cases', () => { + assert.sameDeepMembers(calculateUniqueKeys(null), + []); + assert.sameDeepMembers(calculateUniqueKeys([]), + []); + }); + it('should handle some real cases', () => { + assert.sameDeepMembers(calculateUniqueKeys([ + { id: 'a', to: 'a' }, + { id: 'a', to: 'a' }, // dup + { id: 'b', to: 'b' }, + { id: 'a', to: 'å' }, // override + ]),[ + { id: 'b', to: 'b' }, + { id: 'a', to: 'å' }, + ]); + }); + }); + + describe('allUsedKeyIdsInLayers()', () => { + it('should handle a null case', () => { + assert.sameDeepMembers(Array.from(allUsedKeyIdsInLayers(null).values()), + []); + }); + it('should handle a real case', () => { + assert.sameDeepMembers(Array.from(allUsedKeyIdsInLayers([ + { + layer: [ + { + row: [ + { keys: 'q w e r t y' }, + { keys: 'a s d f' }, + ] + }, + { + row: [ + { keys: 'Q W E R T Y' }, + { keys: 'A S D F' }, + ] + } + ] + }, + { + layer: [ + { + row: [ + { keys: 'q w e r t y' }, + { keys: '0 1 2 3' } + ] + }, + ] + } + ]).values()), + 'q w e r t y Q W E R T Y a s d f A S D F 0 1 2 3'.split(' ')); + }); + }); + describe('translateLayerAttrToModifier', () => { + it('should map from layer info to modifier number', () => { + assert.equal(translateLayerAttrToModifier({ + id: 'base', + }), constants.keys_mod_none); + assert.equal(translateLayerAttrToModifier({ + id: 'base', + modifier: '', + }), constants.keys_mod_none); + assert.equal(translateLayerAttrToModifier({ + id: 'base', + modifier: 'none', + }), constants.keys_mod_none); + assert.equal(translateLayerAttrToModifier({ + id: 'shift', + modifier: 'shift', + }), constants.keys_mod_shift); + assert.equal(translateLayerAttrToModifier({ + id: 'shift', + modifier: 'shift', + }), constants.keys_mod_shift); + assert.equal(translateLayerAttrToModifier({ + id: 'altshift', + modifier: 'alt shift', + }), constants.keys_mod_alt | constants.keys_mod_shift); + }); + it('should round trip each possible modifier', () => { + for(let str of constants.keys_mod_map.keys()) { + const layer : LDMLKeyboard.LKLayer = { + id: str, + modifier: `${str}`, + }; + assert.equal(translateLayerAttrToModifier(layer), + constants.keys_mod_map.get(str), str); + } + }); + it('should round trip each possible modifier with altL', () => { + for(let str of constants.keys_mod_map.keys()) { + const layer : LDMLKeyboard.LKLayer = { + id: str, + modifier: `${str} altL`, + }; + assert.equal(translateLayerAttrToModifier(layer), + constants.keys_mod_map.get(str) | constants.keys_mod_altL, str); + } + }); + }); + describe('isValidModifier()', () => { + it('should treat falsy values as valid', () => { + for(let str of [ + null, undefined, '', 'none' + ]) { + assert.ok(validModifier(str), `validModifier(${JSON.stringify(str)})`); + } + }); + it('should treat bad values as invalid', () => { + for(let str of [ + 'asdfasdf', 'shift asdfasdf', 'altR-shift', 'altR-shift shift' + ]) { + assert.notOk(validModifier(str), `validModifier(${JSON.stringify(str)})`); + } + }); + }); +}); diff --git a/developer/src/kmc-keyboard/test/test-virtual-key-constants.ts b/developer/src/kmc-keyboard/test/test-virtual-key-constants.ts new file mode 100644 index 00000000000..3ad5d3662cc --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-virtual-key-constants.ts @@ -0,0 +1,70 @@ +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]); + } + }); +}); diff --git a/developer/src/kmc-keyboard/test/test-visual-keyboard-compiler-e2e.ts b/developer/src/kmc-keyboard/test/test-visual-keyboard-compiler-e2e.ts new file mode 100644 index 00000000000..1b1e34069e5 --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-visual-keyboard-compiler-e2e.ts @@ -0,0 +1,33 @@ +import 'mocha'; +import {assert} from 'chai'; +import x_hextobin from '@keymanapp/hextobin'; +import { KvkFileWriter } from '@keymanapp/common-types'; +import {checkMessages, 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 + + it('should build fixtures', async function() { + // Let's build basic.xml + // It should match basic.kvk (built from basic-kvk.txt) + + const inputFilename = makePathToFixture('basic.xml'); + const binaryFilename = makePathToFixture('basic-kvk.txt'); + + // Compile the visual keyboard + const vk = compileVisualKeyboard(inputFilename, {debug: true, addCompilerVersion: false}); + assert.isNotNull(vk); + + // Use the builder to generate the binary output file + const writer = new KvkFileWriter(); + const code = writer.write(vk); + checkMessages(); + assert.isNotNull(code); + + // Compare output + let expected = await hextobin(binaryFilename, undefined, {silent:true}); + assert.deepEqual(code, expected); + }); +}); \ No newline at end of file diff --git a/developer/src/kmc-keyboard/test/test-vkey.ts b/developer/src/kmc-keyboard/test/test-vkey.ts new file mode 100644 index 00000000000..185754c976f --- /dev/null +++ b/developer/src/kmc-keyboard/test/test-vkey.ts @@ -0,0 +1,62 @@ +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', function() { + let vkey = 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', function() { + let vkey = 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', function() { + let vkey = 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 error on invalid "from" vkey', function() { + let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-from-vkey.xml', compilerTestCallbacks) as Vkey; + assert.isNull(vkey); + assert.equal(compilerTestCallbacks.messages.length, 2); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_VkeyIsNotValid({vkey: 'q'})); + assert.deepEqual(compilerTestCallbacks.messages[1], CompilerMessages.Error_VkeyIsNotValid({vkey: 'HYFEN'})); + }); + + it('should error on invalid "to" vkey', function() { + let vkey = loadSectionFixture(VkeyCompiler, 'sections/vkey/invalid-to-vkey.xml', compilerTestCallbacks) as Vkey; + assert.isNull(vkey); + assert.equal(compilerTestCallbacks.messages.length, 1); + assert.deepEqual(compilerTestCallbacks.messages[0], CompilerMessages.Error_VkeyIsNotValid({vkey: 'A-ACUTE'})); + }); + + it('should error on repeated vkeys', function() { + let vkey = 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'})); + }); +}); + diff --git a/developer/src/kmc-keyboard/test/tsconfig.json b/developer/src/kmc-keyboard/test/tsconfig.json new file mode 100644 index 00000000000..263cdd0d945 --- /dev/null +++ b/developer/src/kmc-keyboard/test/tsconfig.json @@ -0,0 +1,26 @@ +{ + "extends": "../../kmc/tsconfig.kmc-base.json", + + "compilerOptions": { + "rootDir": ".", + "rootDirs": ["./", "../src/"], + "outDir": "../build/test", + "baseUrl": ".", + "strictNullChecks": false, // TODO: get rid of this as some point + "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"], + }, + }, + "include": [ + "**/test-*.ts", + "./helpers/index.ts" + ], + "references": [ + { "path": "../../../../common/web/keyman-version/tsconfig.esm.json" }, + { "path": "../../../../common/web/types/" }, + { "path": "../../../../common/tools/hextobin/" }, + { "path": "../" } + ] +} \ No newline at end of file diff --git a/developer/src/kmc-keyboard/tsconfig.json b/developer/src/kmc-keyboard/tsconfig.json new file mode 100644 index 00000000000..a9dcdbbb2a8 --- /dev/null +++ b/developer/src/kmc-keyboard/tsconfig.json @@ -0,0 +1,25 @@ +{ + "extends": "../../../tsconfig.esm-base.json", + + "compilerOptions": { + "outDir": "build/src/", + "rootDir": "src/", + "baseUrl": ".", + "allowSyntheticDefaultImports": true, // for ajv + + "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"], + }, + + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { "path": "../../../common/web/keyman-version/tsconfig.esm.json" }, + { "path": "../../../common/web/types/" }, + { "path": "../../../core/include/ldml/"}, + ] +} diff --git a/developer/src/kmc-model-info/Makefile b/developer/src/kmc-model-info/Makefile new file mode 100644 index 00000000000..f3e73efd123 --- /dev/null +++ b/developer/src/kmc-model-info/Makefile @@ -0,0 +1,38 @@ +# +# Keyman Developer - kmc Model-Info Compiler Makefile +# + +!include ..\Defines.mak + +# We don't depend on configure here because kmc-keyboard does that already +build: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh build + +configure: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh configure + +clean: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh clean + +test: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh test + +# build.sh bundle must be run from shell as it requires a temp folder to be +# passed in. See inst/download.in.mak for instantiation. + +publish: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh publish + +signcode: + @rem nothing to do + +wrap-symbols: + @rem nothing to do + +test-manifest: + @rem nothing to do + +install: + @rem nothing to do + +!include ..\Target.mak diff --git a/developer/src/kmc-model-info/build.sh b/developer/src/kmc-model-info/build.sh new file mode 100755 index 00000000000..1a877cb8540 --- /dev/null +++ b/developer/src/kmc-model-info/build.sh @@ -0,0 +1,67 @@ +#!/usr/bin/env bash +# +# Compiles the kmc lexical model model-info compiler. +# + +# 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 + +cd "$THIS_SCRIPT_PATH" + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +builder_describe "Build Keyman kmc Lexical Model model-info Compiler module" \ + "@../kmc-package" \ + "configure" \ + "build" \ + "clean" \ + "test" \ + "publish publish to npm" \ + "--dry-run,-n don't actually publish, just dry run" +builder_describe_outputs \ + configure /node_modules \ + build build/src/model-info-compiler.js + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action clean; then + rm -rf ./build/ ./tsconfig.tsbuildinfo + 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 + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action test; then + #npm test - no tests as yet + builder_finish_action success test +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action publish; then + . "$KEYMAN_ROOT/resources/build/npm-publish.inc.sh" + npm_publish + builder_finish_action success publish +fi diff --git a/developer/src/kmc-model-info/package.json b/developer/src/kmc-model-info/package.json new file mode 100644 index 00000000000..9b299d9629e --- /dev/null +++ b/developer/src/kmc-model-info/package.json @@ -0,0 +1,56 @@ +{ + "name": "@keymanapp/kmc-model-info", + "description": "Keyman Developer .model_info compiler", + "keywords": [ + "keyboard", + "keyman", + "unicode", + "lexical-model", + "predictive-text" + ], + "type": "module", + "exports": { + ".": "./build/src/model-info-compiler.js" + }, + "scripts": { + "build": "tsc -b", + "test": "cd test && tsc -b && cd .. && c8 --reporter=lcov --reporter=text mocha", + "prepublishOnly": "npm run build" + }, + "author": "Marc Durdin (https://github.com/mcdurdin)", + "contributors": [ + "Eddie Antonio Santos ", + "Joshua Horton" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/keymanapp/keyman/issues" + }, + "dependencies": { + "@keymanapp/keyman-version": "*", + "@keymanapp/kmc-package": "*", + "@keymanapp/models-types": "*" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "^4.5.4" + }, + "mocha": { + "spec": "build/test/**/test-*.js", + "require": [ + "source-map-support/register" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" + } +} diff --git a/developer/src/kmlmc/source/util/min-keyman-version.ts b/developer/src/kmc-model-info/src/min-keyman-version.ts similarity index 100% rename from developer/src/kmlmc/source/util/min-keyman-version.ts rename to developer/src/kmc-model-info/src/min-keyman-version.ts diff --git a/developer/src/kmlmc/source/model-info-compiler/model-info-compiler.ts b/developer/src/kmc-model-info/src/model-info-compiler.ts similarity index 93% rename from developer/src/kmlmc/source/model-info-compiler/model-info-compiler.ts rename to developer/src/kmc-model-info/src/model-info-compiler.ts index e8606b0dc63..8c9b10db4d3 100644 --- a/developer/src/kmlmc/source/model-info-compiler/model-info-compiler.ts +++ b/developer/src/kmc-model-info/src/model-info-compiler.ts @@ -3,14 +3,13 @@ * compiled files to produce a comprehensive .model_info file. */ -/// -/// +/// +/// import * as fs from "fs"; import * as path from "path"; -import { minKeymanVersion } from "../util/min-keyman-version"; -import {SysExits} from "../util/sysexits"; - +import { minKeymanVersion } from "./min-keyman-version.js"; +// import KmpJsonFile from "@keymanapp/kmc-package/kmp-json-file"; export class ModelInfoOptions { /** The identifier for the model */ @@ -96,8 +95,7 @@ export function writeMergedModelMetadataFile( // we strip the mailto: from the .kps file for the .model_info let match = author.url.match(/^(mailto\:)?(.+)$/); if (match === null) { - console.error(`Invalid author email: ${author.url}`); - process.exit(SysExits.EX_DATAERR); + throw new Error(`Invalid author email: ${author.url}`); } let email = match[2]; diff --git a/developer/src/kmlmc/source/lexical-model-compiler/model-info-file.ts b/developer/src/kmc-model-info/src/model-info-file.ts similarity index 100% rename from developer/src/kmlmc/source/lexical-model-compiler/model-info-file.ts rename to developer/src/kmc-model-info/src/model-info-file.ts diff --git a/developer/src/kmc-model-info/tsconfig.json b/developer/src/kmc-model-info/tsconfig.json new file mode 100644 index 00000000000..1609623edfc --- /dev/null +++ b/developer/src/kmc-model-info/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../kmc/tsconfig.kmc-base.json", + + "compilerOptions": { + "outDir": "build/src/", + "rootDir": "src/", + "baseUrl": ".", + "paths": { + } + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { "path": "../kmc-package" } // can we eliminate this in future? + ] +} diff --git a/developer/src/kmc-model/Makefile b/developer/src/kmc-model/Makefile new file mode 100644 index 00000000000..353d923bd44 --- /dev/null +++ b/developer/src/kmc-model/Makefile @@ -0,0 +1,38 @@ +# +# Keyman Developer - kmc Model Compiler Makefile +# + +!include ..\Defines.mak + +# We don't depend on configure here because kmc-keyboard does that already +build: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh build + +configure: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh configure + +clean: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh clean + +test: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh test + +# build.sh bundle must be run from shell as it requires a temp folder to be +# passed in. See inst/download.in.mak for instantiation. + +publish: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh publish + +signcode: + @rem nothing to do + +wrap-symbols: + @rem nothing to do + +test-manifest: + @rem nothing to do + +install: + @rem nothing to do + +!include ..\Target.mak diff --git a/developer/src/kmc-model/build.sh b/developer/src/kmc-model/build.sh new file mode 100755 index 00000000000..51aef36cea7 --- /dev/null +++ b/developer/src/kmc-model/build.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# +# Compiles the kmc lexical model compiler. +# + +# 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 + +cd "$THIS_SCRIPT_PATH" + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +# TODO: "@/common/models/types" \ + +builder_describe "Build Keyman kmc Lexical Model Compiler module" \ + "@/common/web/keyman-version" \ + "configure" \ + "build" \ + "clean" \ + "test" \ + "publish publish to npm" \ + "--dry-run,-n don't actually publish, just dry run" +builder_describe_outputs \ + configure /node_modules \ + build build/src/main.js + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action clean; then + rm -rf ./build/ ./tsconfig.tsbuildinfo + 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 + # Note: build-cjs only emits lexical-model-compiler.cjs at this time, as that + # is the only file required by other non-ES modules + # (common/web/input-processor tests) + mkdir -p build/cjs-src + npm run build + builder_finish_action success build +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action test; then + npm test + builder_finish_action success test +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action publish; then + . "$KEYMAN_ROOT/resources/build/npm-publish.inc.sh" + npm_publish + builder_finish_action success publish +fi diff --git a/developer/src/kmlmc/package.json b/developer/src/kmc-model/package.json similarity index 61% rename from developer/src/kmlmc/package.json rename to developer/src/kmc-model/package.json index c6ab80198ff..9ea9ebefb6b 100644 --- a/developer/src/kmlmc/package.json +++ b/developer/src/kmc-model/package.json @@ -1,22 +1,23 @@ { - "name": "@keymanapp/lexical-model-compiler", + "name": "@keymanapp/kmc-model", "description": "Keyman Developer lexical model compiler", "keywords": [ "keyboard", - "predictive-text", "keyman", + "unicode", "lexical-model", - "unicode" + "predictive-text" ], + "type": "module", + "exports": { + ".": "./build/src/main.js" + }, "scripts": { - "build": "tsc -b", - "test": "cd tests && tsc -b && cd .. && mocha", + "build": "tsc -b && npm run build-cjs", + "build-cjs": "esbuild build/src/lexical-model-compiler.js --bundle --platform=node --external:../../node_modules/* > build/cjs-src/lexical-model-compiler.cjs", + "test": "cd test && tsc -b && cd .. && c8 --reporter=lcov --reporter=text mocha", "prepublishOnly": "npm run build" }, - "repository": { - "type": "git", - "url": "https://github.com/keymanapp/keyman" - }, "author": "Marc Durdin (https://github.com/mcdurdin)", "contributors": [ "Eddie Antonio Santos ", @@ -26,19 +27,9 @@ "bugs": { "url": "https://github.com/keymanapp/keyman/issues" }, - "homepage": "https://github.com/keymanapp/keyman/tree/master/common/predictive-text#readme", - "bin": { - "kmlmc": "dist/kmlmc.js", - "kmlmp": "dist/kmlmp.js", - "kmlmi": "dist/kmlmi.js" - }, - "engines": { - "node": ">=16.0.0" - }, "dependencies": { - "@keymanapp/models-types": "*", "@keymanapp/keyman-version": "*", - "commander": "^3.0.0", + "@keymanapp/models-types": "*", "typescript": "^4.9.5", "xml2js": "^0.4.19" }, @@ -49,13 +40,21 @@ "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", - "jszip": "^3.7.0", + "esbuild": "^0.15.7", "mocha": "^10.0.0", "ts-node": "^10.9.1" }, "mocha": { - "spec": "dist-tests/**/test-*.js" + "spec": "build/test/**/test-*.js", + "require": [ + "source-map-support/register" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" } } diff --git a/developer/src/kmlmc/source/lexical-model-compiler/build-trie.ts b/developer/src/kmc-model/src/build-trie.ts similarity index 99% rename from developer/src/kmlmc/source/lexical-model-compiler/build-trie.ts rename to developer/src/kmc-model/src/build-trie.ts index 8bf49a51243..75dc2db592b 100644 --- a/developer/src/kmlmc/source/lexical-model-compiler/build-trie.ts +++ b/developer/src/kmc-model/src/build-trie.ts @@ -1,5 +1,5 @@ import { readFileSync } from "fs"; -import { log, KeymanCompilerError } from "../errors"; +import { log, KeymanCompilerError } from "./model-compiler-errors.js"; // Supports LF or CRLF line terminators. const NEWLINE_SEPARATOR = /\u000d?\u000a/; diff --git a/developer/src/kmlmc/source/lexical-model-compiler/join-word-breaker-decorator.ts b/developer/src/kmc-model/src/join-word-breaker-decorator.ts similarity index 100% rename from developer/src/kmlmc/source/lexical-model-compiler/join-word-breaker-decorator.ts rename to developer/src/kmc-model/src/join-word-breaker-decorator.ts diff --git a/developer/src/kmlmc/source/lexical-model-compiler/lexical-model-compiler.ts b/developer/src/kmc-model/src/lexical-model-compiler.ts similarity index 95% rename from developer/src/kmlmc/source/lexical-model-compiler/lexical-model-compiler.ts rename to developer/src/kmc-model/src/lexical-model-compiler.ts index 78a25cd9592..0c55242c475 100644 --- a/developer/src/kmlmc/source/lexical-model-compiler/lexical-model-compiler.ts +++ b/developer/src/kmc-model/src/lexical-model-compiler.ts @@ -3,15 +3,15 @@ */ /// -/// import * as ts from "typescript"; import * as fs from "fs"; import * as path from "path"; -import { createTrieDataStructure } from "./build-trie"; -import { ModelDefinitions } from "./model-definitions"; -import {decorateWithJoin} from "./join-word-breaker-decorator"; -import {decorateWithScriptOverrides} from "./script-overrides-decorator"; +import { createTrieDataStructure } from "./build-trie.js"; +import { ModelDefinitions } from "./model-definitions.js"; +import {decorateWithJoin} from "./join-word-breaker-decorator.js"; +import {decorateWithScriptOverrides} from "./script-overrides-decorator.js"; +import { LexicalModelSource, WordBreakerSpec, SimpleWordBreakerSpec } from "./lexical-model.js"; export default class LexicalModelCompiler { diff --git a/developer/src/kmlmc/source/lexical-model-compiler/lexical-model.ts b/developer/src/kmc-model/src/lexical-model.ts similarity index 87% rename from developer/src/kmlmc/source/lexical-model-compiler/lexical-model.ts rename to developer/src/kmc-model/src/lexical-model.ts index e1b55266d03..794d7763b22 100644 --- a/developer/src/kmlmc/source/lexical-model-compiler/lexical-model.ts +++ b/developer/src/kmc-model/src/lexical-model.ts @@ -3,7 +3,7 @@ * the LMLayer's internal worker code, so we provide those definitions too. */ -interface LexicalModelDeclaration { +export interface LexicalModelDeclaration { readonly format: 'trie-1.0'|'fst-foma-1.0'|'custom-1.0', //... metadata ... } @@ -16,7 +16,7 @@ interface LexicalModelDeclaration { * * @since 14.0 */ -interface WordBreakerSpec { + export interface WordBreakerSpec { readonly use: SimpleWordBreakerSpec; /** * If present, joins words that were split by the word breaker @@ -43,27 +43,27 @@ interface WordBreakerSpec { * * @since 11.0 */ -type SimpleWordBreakerSpec = 'default' | 'ascii' | WordBreakingFunction; + export type SimpleWordBreakerSpec = 'default' | 'ascii' | WordBreakingFunction; /** * Simplifies input text to facilitate finding entries within a lexical model's * lexicon. * @since 11.0 */ -type SimpleWordformToKeySpec = (term: string) => string; + export type SimpleWordformToKeySpec = (term: string) => string; /** * Simplifies input text to facilitate finding entries within a lexical model's * lexicon, using the model's `applyCasing` function to assist in the keying process. * @since 14.0 */ - type CasedWordformToKeySpec = (term: string, applyCasing?: CasingFunction) => string; +export type CasedWordformToKeySpec = (term: string, applyCasing?: CasingFunction) => string; /** * Simplifies input text to facilitate finding entries within a lexical model's * lexicon. */ -type WordformToKeySpec = SimpleWordformToKeySpec | CasedWordformToKeySpec; + export type WordformToKeySpec = SimpleWordformToKeySpec | CasedWordformToKeySpec; /** * Override the default word breaking behaviour for some scripts. @@ -89,10 +89,10 @@ type WordformToKeySpec = SimpleWordformToKeySpec | CasedWordformToKeySpec; * * @since 14.0 */ -type OverrideScriptDefaults = 'break-words-at-spaces'; +export type OverrideScriptDefaults = 'break-words-at-spaces'; -interface LexicalModelSource extends LexicalModelDeclaration { +export interface LexicalModelSource extends LexicalModelDeclaration { readonly sources: Array; /** * The name of the type to instantiate (without parameters) as the base object for a custom predictive model. @@ -114,7 +114,7 @@ interface LexicalModelSource extends LexicalModelDeclaration { * - 'initial' -- a version preserving the input casing aside from the initial character, * which is uppercased (like with proper nouns and sentence-initial words in English * sentences.) - * + * * This is only utilized if `languageUsesCasing` is defined and set to `true`. * @since 14.0 */ @@ -145,6 +145,6 @@ interface LexicalModelSource extends LexicalModelDeclaration { readonly punctuation?: LexicalModelPunctuation; } -interface LexicalModelCompiled extends LexicalModelDeclaration { +export interface LexicalModelCompiled extends LexicalModelDeclaration { readonly id: string; } diff --git a/developer/src/kmlmc/source/util/util.ts b/developer/src/kmc-model/src/main.ts similarity index 80% rename from developer/src/kmlmc/source/util/util.ts rename to developer/src/kmc-model/src/main.ts index 15e177fb05c..5b1bb7fe7fb 100644 --- a/developer/src/kmlmc/source/util/util.ts +++ b/developer/src/kmc-model/src/main.ts @@ -1,11 +1,9 @@ -// TODO: this file is really only for lexical model compiler tests. Find a good name. -// import * as fs from 'fs'; import * as path from 'path'; -import * as ts from 'typescript'; +import ts from 'typescript'; -import { SysExits } from "./sysexits"; -import LexicalModelCompiler from '../lexical-model-compiler/lexical-model-compiler'; +import LexicalModelCompiler from './lexical-model-compiler.js'; +import { LexicalModelSource } from './lexical-model.js'; /** * Compiles a model.ts file, using paths relative to its location. @@ -57,9 +55,7 @@ export function loadFromFilename(filename: string): LexicalModelSource { module(moduleExports); if (!moduleExports['__esModule'] || !moduleExports['default']) { - console.error(`Model source '${filename}' does have a default export. Did you remember to write \`export default source;\`?`); - // TODO: throw an Error instead. - process.exit(SysExits.EX_DATAERR); + throw new Error(`Model source '${filename}' does have a default export. Did you remember to write \`export default source;\`?`); } return moduleExports['default'] as LexicalModelSource; diff --git a/developer/src/kmlmc/source/errors.ts b/developer/src/kmc-model/src/model-compiler-errors.ts similarity index 99% rename from developer/src/kmlmc/source/errors.ts rename to developer/src/kmc-model/src/model-compiler-errors.ts index f58c2df91bc..20aa02d8e80 100644 --- a/developer/src/kmlmc/source/errors.ts +++ b/developer/src/kmc-model/src/model-compiler-errors.ts @@ -1,3 +1,4 @@ +// TODO: merge with kmc keyboard-compiler errors /** * Log levels. * diff --git a/developer/src/kmlmc/source/lexical-model-compiler/model-defaults.ts b/developer/src/kmc-model/src/model-defaults.ts similarity index 100% rename from developer/src/kmlmc/source/lexical-model-compiler/model-defaults.ts rename to developer/src/kmc-model/src/model-defaults.ts diff --git a/developer/src/kmlmc/source/lexical-model-compiler/model-definitions.ts b/developer/src/kmc-model/src/model-definitions.ts similarity index 97% rename from developer/src/kmlmc/source/lexical-model-compiler/model-definitions.ts rename to developer/src/kmc-model/src/model-definitions.ts index 4ab6fcad1ee..9a9f325eb24 100644 --- a/developer/src/kmlmc/source/lexical-model-compiler/model-definitions.ts +++ b/developer/src/kmc-model/src/model-definitions.ts @@ -1,9 +1,10 @@ import { defaultApplyCasing, defaultCasedSearchTermToKey, defaultSearchTermToKey - } from "./model-defaults"; + } from "./model-defaults.js"; -const KEYMAN_VERSION = require("@keymanapp/keyman-version").KEYMAN_VERSION; +import KEYMAN_VERSION from "@keymanapp/keyman-version/keyman-version.mjs"; +import { LexicalModelSource, WordformToKeySpec } from "./lexical-model.js"; /** * Processes certain defined model behaviors in such a way that the needed closures diff --git a/developer/src/kmlmc/source/lexical-model-compiler/script-overrides-decorator.ts b/developer/src/kmc-model/src/script-overrides-decorator.ts similarity index 94% rename from developer/src/kmlmc/source/lexical-model-compiler/script-overrides-decorator.ts rename to developer/src/kmc-model/src/script-overrides-decorator.ts index f53851146f9..59a425620cf 100644 --- a/developer/src/kmlmc/source/lexical-model-compiler/script-overrides-decorator.ts +++ b/developer/src/kmc-model/src/script-overrides-decorator.ts @@ -1,4 +1,4 @@ -/// +import { OverrideScriptDefaults } from "./lexical-model"; export function decorateWithScriptOverrides(breaker: WordBreakingFunction, option: OverrideScriptDefaults) { if (option !== 'break-words-at-spaces') { @@ -9,7 +9,7 @@ export function decorateWithScriptOverrides(breaker: WordBreakingFunction, optio * Matches if when a span contains a Southeast-Asian letter or mark anywhere. * This makes it a candidate for joining. * - * See: libexec/create-override-script-regexp.ts for how this RegExp was + * See: tools/create-override-script-regexp.ts for how this RegExp was * generated. * * Last updated for Unicode 13.0.0. diff --git a/developer/src/kmlmc/tests/README.md b/developer/src/kmc-model/test/README.md similarity index 100% rename from developer/src/kmlmc/tests/README.md rename to developer/src/kmc-model/test/README.md diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.joinwordbreaker/example.qaa.joinwordbreaker.model.ts b/developer/src/kmc-model/test/fixtures/example.qaa.joinwordbreaker/example.qaa.joinwordbreaker.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.joinwordbreaker/example.qaa.joinwordbreaker.model.ts rename to developer/src/kmc-model/test/fixtures/example.qaa.joinwordbreaker/example.qaa.joinwordbreaker.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.joinwordbreaker/wordlist.tsv b/developer/src/kmc-model/test/fixtures/example.qaa.joinwordbreaker/wordlist.tsv similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.joinwordbreaker/wordlist.tsv rename to developer/src/kmc-model/test/fixtures/example.qaa.joinwordbreaker/wordlist.tsv diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.scriptusesspaces/example.qaa.scriptusesspaces.model.ts b/developer/src/kmc-model/test/fixtures/example.qaa.scriptusesspaces/example.qaa.scriptusesspaces.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.scriptusesspaces/example.qaa.scriptusesspaces.model.ts rename to developer/src/kmc-model/test/fixtures/example.qaa.scriptusesspaces/example.qaa.scriptusesspaces.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.scriptusesspaces/wordlist.tsv b/developer/src/kmc-model/test/fixtures/example.qaa.scriptusesspaces/wordlist.tsv similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.scriptusesspaces/wordlist.tsv rename to developer/src/kmc-model/test/fixtures/example.qaa.scriptusesspaces/wordlist.tsv diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.intermediate.json b/developer/src/kmc-model/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.json similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.intermediate.json rename to developer/src/kmc-model/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.json diff --git a/developer/src/kmc-model/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kps b/developer/src/kmc-model/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kps new file mode 100644 index 00000000000..571e710c949 --- /dev/null +++ b/developer/src/kmc-model/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kps @@ -0,0 +1,35 @@ + + + + 12.0.1500.0 + 12.0 + + + + + + SENĆOŦEN (Saanich Dialect) Lexical Model + © 2019 National Research Council Canada + Eddie Antonio Santos + 1.0.3 + + + + ..\build\example.qaa.sencoten.model.js + Lexical model example.qaa.sencoten.model.js + 0 + .model.js + + + + + SENĆOŦEN dictionary + example.qaa.sencoten + 1.0.3 + + North Straits Salish + SENĆOŦEN + + + + diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.ts b/developer/src/kmc-model/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.ts rename to developer/src/kmc-model/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/wordlist.tsv b/developer/src/kmc-model/test/fixtures/example.qaa.sencoten/wordlist.tsv similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/wordlist.tsv rename to developer/src/kmc-model/test/fixtures/example.qaa.sencoten/wordlist.tsv diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.smp/example.qaa.smp.model.ts b/developer/src/kmc-model/test/fixtures/example.qaa.smp/example.qaa.smp.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.smp/example.qaa.smp.model.ts rename to developer/src/kmc-model/test/fixtures/example.qaa.smp/example.qaa.smp.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.smp/wordlist.tsv b/developer/src/kmc-model/test/fixtures/example.qaa.smp/wordlist.tsv similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.smp/wordlist.tsv rename to developer/src/kmc-model/test/fixtures/example.qaa.smp/wordlist.tsv diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.trivial/example.qaa.trivial.model.ts b/developer/src/kmc-model/test/fixtures/example.qaa.trivial/example.qaa.trivial.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.trivial/example.qaa.trivial.model.ts rename to developer/src/kmc-model/test/fixtures/example.qaa.trivial/example.qaa.trivial.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.trivial/wordlist.tsv b/developer/src/kmc-model/test/fixtures/example.qaa.trivial/wordlist.tsv similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.trivial/wordlist.tsv rename to developer/src/kmc-model/test/fixtures/example.qaa.trivial/wordlist.tsv diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.utf16be/example.qaa.utf16be.model.ts b/developer/src/kmc-model/test/fixtures/example.qaa.utf16be/example.qaa.utf16be.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.utf16be/example.qaa.utf16be.model.ts rename to developer/src/kmc-model/test/fixtures/example.qaa.utf16be/example.qaa.utf16be.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.utf16be/wordlist.txt b/developer/src/kmc-model/test/fixtures/example.qaa.utf16be/wordlist.txt similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.utf16be/wordlist.txt rename to developer/src/kmc-model/test/fixtures/example.qaa.utf16be/wordlist.txt diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.utf16le/example.qaa.utf16le.model.ts b/developer/src/kmc-model/test/fixtures/example.qaa.utf16le/example.qaa.utf16le.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.utf16le/example.qaa.utf16le.model.ts rename to developer/src/kmc-model/test/fixtures/example.qaa.utf16le/example.qaa.utf16le.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.utf16le/wordlist.txt b/developer/src/kmc-model/test/fixtures/example.qaa.utf16le/wordlist.txt similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.utf16le/wordlist.txt rename to developer/src/kmc-model/test/fixtures/example.qaa.utf16le/wordlist.txt diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.wordbreaker/example.qaa.wordbreaker.model.ts b/developer/src/kmc-model/test/fixtures/example.qaa.wordbreaker/example.qaa.wordbreaker.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.wordbreaker/example.qaa.wordbreaker.model.ts rename to developer/src/kmc-model/test/fixtures/example.qaa.wordbreaker/example.qaa.wordbreaker.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.wordbreaker/wordlist.tsv b/developer/src/kmc-model/test/fixtures/example.qaa.wordbreaker/wordlist.tsv similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.wordbreaker/wordlist.tsv rename to developer/src/kmc-model/test/fixtures/example.qaa.wordbreaker/wordlist.tsv diff --git a/developer/src/kmlmc/tests/helpers/index.ts b/developer/src/kmc-model/test/helpers/index.ts similarity index 94% rename from developer/src/kmlmc/tests/helpers/index.ts rename to developer/src/kmc-model/test/helpers/index.ts index f09f438e2f2..cc6affa6930 100644 --- a/developer/src/kmlmc/tests/helpers/index.ts +++ b/developer/src/kmc-model/test/helpers/index.ts @@ -4,8 +4,9 @@ * Helpers and utilities for the Mocha tests. */ import * as path from 'path'; +import { fileURLToPath } from 'url'; import {assert} from 'chai'; -import {LogMessage, KeymanCompilerError, redirectLogMessagesTo, resetLogMessageHandler, LogLevel} from '../../dist/errors'; +import {LogMessage, KeymanCompilerError, redirectLogMessagesTo, resetLogMessageHandler, LogLevel} from '../../src/model-compiler-errors.js'; export interface CompilationResult { hasSyntaxError: boolean; @@ -22,8 +23,8 @@ export interface CompilationResult { * * @param components One or more path components. */ -export function makePathToFixture(...components: string[]): string { - return path.join(__dirname, '..', '..', 'tests', 'fixtures', ...components); + export function makePathToFixture(...components: string[]): string { + return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url)); } /** diff --git a/developer/src/kmlmc/tests/test-compile-model-with-pseudoclosure.ts b/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts similarity index 98% rename from developer/src/kmlmc/tests/test-compile-model-with-pseudoclosure.ts rename to developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts index 391cbbae37e..95b8ee37228 100644 --- a/developer/src/kmlmc/tests/test-compile-model-with-pseudoclosure.ts +++ b/developer/src/kmc-model/test/test-compile-model-with-pseudoclosure.ts @@ -1,9 +1,8 @@ -import LexicalModelCompiler from '../dist/lexical-model-compiler/lexical-model-compiler'; +import LexicalModelCompiler from '../src/lexical-model-compiler.js'; import {assert} from 'chai'; import 'mocha'; -import { compileModelSourceCode } from './helpers'; -import { makePathToFixture } from './helpers'; +import { makePathToFixture, compileModelSourceCode } from './helpers/index.js'; describe('LexicalModelCompiler - pseudoclosure compilation + use', function () { const MODEL_ID = 'example.qaa.trivial'; diff --git a/developer/src/kmlmc/tests/test-compile-model.ts b/developer/src/kmc-model/test/test-compile-model.ts similarity index 92% rename from developer/src/kmlmc/tests/test-compile-model.ts rename to developer/src/kmc-model/test/test-compile-model.ts index 9003ac13303..d98f0e86680 100644 --- a/developer/src/kmlmc/tests/test-compile-model.ts +++ b/developer/src/kmc-model/test/test-compile-model.ts @@ -1,8 +1,8 @@ import 'mocha'; import {assert} from 'chai'; -import {compileModel} from '../dist/util/util'; -import {makePathToFixture, compileModelSourceCode, CompilationResult} from './helpers'; +import {compileModel} from '../src/main.js'; +import {makePathToFixture, compileModelSourceCode, CompilationResult} from './helpers/index.js'; describe('compileModel', function () { // Try to compile ALL of the correct models. diff --git a/developer/src/kmlmc/tests/test-compile-trie.ts b/developer/src/kmc-model/test/test-compile-trie.ts similarity index 96% rename from developer/src/kmlmc/tests/test-compile-trie.ts rename to developer/src/kmc-model/test/test-compile-trie.ts index b487d055b33..d02b24eddf0 100644 --- a/developer/src/kmlmc/tests/test-compile-trie.ts +++ b/developer/src/kmc-model/test/test-compile-trie.ts @@ -1,9 +1,9 @@ -import LexicalModelCompiler from '../dist/lexical-model-compiler/lexical-model-compiler'; +import LexicalModelCompiler from '../src/lexical-model-compiler.js'; import {assert} from 'chai'; import 'mocha'; -import {makePathToFixture, compileModelSourceCode} from './helpers'; -import { createTrieDataStructure } from '../dist/lexical-model-compiler/build-trie'; +import {makePathToFixture, compileModelSourceCode} from './helpers/index.js'; +import { createTrieDataStructure } from '../src/build-trie.js'; describe('LexicalModelCompiler', function () { describe('#generateLexicalModelCode', function () { @@ -100,7 +100,7 @@ describe('LexicalModelCompiler', function () { } else if(code.charCodeAt(i) >= 0xDC00 && code.charCodeAt(i) < 0xE000) { assert.isTrue((code.charCodeAt(i-1) >= 0xD800 && code.charCodeAt(i-1) < 0xDC00), 'Unpaired trail surrogate U+'+code.charCodeAt(i).toString(16)+' at position '+i+' of \''+code+'\''); - } + } } // Sanity check: the word list has three total unweighted words, with a diff --git a/developer/src/kmlmc/tests/test-default-apply-case.ts b/developer/src/kmc-model/test/test-default-apply-case.ts similarity index 97% rename from developer/src/kmlmc/tests/test-default-apply-case.ts rename to developer/src/kmc-model/test/test-default-apply-case.ts index a408c730144..09d1f49d7ae 100644 --- a/developer/src/kmlmc/tests/test-default-apply-case.ts +++ b/developer/src/kmc-model/test/test-default-apply-case.ts @@ -1,7 +1,7 @@ import 'mocha'; import { assert } from 'chai'; -import { defaultApplyCasing } from '../dist/lexical-model-compiler/model-defaults'; +import { defaultApplyCasing } from '../src/model-defaults.js'; describe('The default applyCasing() function', function () { // // -------- @@ -11,7 +11,7 @@ describe('The default applyCasing() function', function () { // let u = function(code: number): string { // var H = Math.floor((code - 0x10000) / 0x400) + 0xD800; // var L = (code - 0x10000) % 0x400 + 0xDC00; - + // return String.fromCharCode(H, L); // } @@ -37,7 +37,7 @@ describe('The default applyCasing() function', function () { // Note: not written the Turkish way. Turns out 'İ'.toLowerCase() decomposes the result, // which would have made for a fairly yucky test. ['Istanbul', 'istanbul'], - + // The DEFAULT function is NOT responsible for understanding the Turkish // case regarding the lowercasing of: // 'I' U+0048 LATIN CAPITAL LETTER I to 'ı' U+0131 LATIN SMALL LETTER DOTLESS I @@ -47,16 +47,16 @@ describe('The default applyCasing() function', function () { // full-width romaji has corresponding capitalized versions: ['AESTHETIC', 'aesthetic'], - + // "skýlos" is Greek for dog 🇬🇷🐶 // starts with an 's' and ends with an 's' // which are DIFFERENT CHARACTERS in lowercased Greek! ['ΣΚΥΛΟΣ', 'σκυλος'], - + // Uncased syntax and numbers should pass through unscathed: ['1234.?!', '1234.?!'] ]; - + for (let [input, expected] of testCases) { it(`should lowercase '${input}' as '${expected}'`, function() { assert.equal(defaultApplyCasing('lower', input), expected); @@ -68,7 +68,7 @@ describe('The default applyCasing() function', function () { const testCases: [string, string][] = [ // Inverse of the corresponding 'lower' test. ['istanbul', 'ISTANBUL'], - + // The DEFAULT function is NOT responsible for understanding the Turkish // case regarding the uppercasing of: // 'ı' U+0131 LATIN SMALL LETTER DOTLESS I to 'I' U+0048 LATIN CAPITAL LETTER I @@ -78,16 +78,16 @@ describe('The default applyCasing() function', function () { // full-width romaji has corresponding capitalized versions: ['aesthetic', 'AESTHETIC'], - + // "skýlos" is Greek for dog 🇬🇷🐶 // starts with an 's' and ends with an 's' // which are DIFFERENT CHARACTERS in lowercased Greek! ['σκυλος', 'ΣΚΥΛΟΣ'], - + // Uncased syntax and numbers should pass through unscathed: ['1234.?!', '1234.?!'] ]; - + for (let [input, expected] of testCases) { it(`should uppercase '${input}' as '${expected}'`, function() { assert.equal(defaultApplyCasing('upper', input), expected); @@ -99,14 +99,14 @@ describe('The default applyCasing() function', function () { const testCases: [string, string][] = [ // Inverse of the corresponding 'lower' test. ['istanbul', 'Istanbul'], - + // The DEFAULT function is NOT responsible for understanding the Turkish // case regarding the uppercasing of: // 'ı' U+0131 LATIN SMALL LETTER DOTLESS I to 'I' U+0048 LATIN CAPITAL LETTER I // For Turkic languages, the recommendation is to make a // custom applyCasing function: ['diyarbakır', 'Diyarbakır'], // The 'i̇' is the decomposed result alluded to for the previous case. - + // full-width romaji has corresponding capitalized versions: ['aesthetic', 'Aesthetic'], @@ -114,11 +114,11 @@ describe('The default applyCasing() function', function () { // starts with an 's' and ends with an 's' // which are DIFFERENT CHARACTERS in lowercased Greek! ['σκυλος', 'Σκυλος'], - + // Uncased syntax and numbers should pass through unscathed: ['1234.?!', '1234.?!'] ]; - + for (let [input, expected] of testCases) { it(`should initial-case '${input}' as '${expected}'`, function() { assert.equal(defaultApplyCasing('initial', input), expected); diff --git a/developer/src/kmlmc/tests/test-default-search-term-to-key.ts b/developer/src/kmc-model/test/test-default-search-term-to-key.ts similarity index 95% rename from developer/src/kmlmc/tests/test-default-search-term-to-key.ts rename to developer/src/kmc-model/test/test-default-search-term-to-key.ts index 5b90b0e48c3..b777c7c79c3 100644 --- a/developer/src/kmlmc/tests/test-default-search-term-to-key.ts +++ b/developer/src/kmc-model/test/test-default-search-term-to-key.ts @@ -1,9 +1,9 @@ import 'mocha'; import {assert} from 'chai'; -import { defaultSearchTermToKey, +import { defaultSearchTermToKey, defaultCasedSearchTermToKey, - defaultApplyCasing } from '../dist/lexical-model-compiler/model-defaults'; + defaultApplyCasing } from '../src/model-defaults.js'; describe('The default searchTermToKey() function', function () { @@ -12,28 +12,28 @@ describe('The default searchTermToKey() function', function () { // "İstanbul" has a U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE. // Without any casing operations, only the I should be altered. ['İstanbul', 'Istanbul'], - + // Similarly... ['DİYARBAKIR', 'DIYARBAKIR'], - + // "skýlos" is Greek for dog 🇬🇷🐶 // starts with an 's' and ends with an 's' // which are DIFFERENT CHARACTERS in lowercased Greek! ['σκύλος', 'σκυλος'], ['ΣΚΥΛΟΣ', 'ΣΚΥΛΟΣ'], - + // full-width romaji is compatibility-canonical with ASCII characters: ['aesthetic', 'aesthetic'], - + // U+212B ANGSTROM SIGN (Å) // U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE (Å) // and should both normalize to 'A' ['\u212B', 'A'], ['\u00C5', 'A'], - + // We should not fall for U+037E GREEK QUESTION MARK's trolling: ['\u037e', ';'], - + // Test presentational forms of Arabic: // U+FE8D ARABIC LETTER ALEF ISOLATED FORM -> U+0627 ARABIC LETTER ALEF // U+FEDF ARABIC LETTER LAM INITIAL FORM -> U+0644 ARABIC LETTER LAM @@ -42,12 +42,12 @@ describe('The default searchTermToKey() function', function () { // U+FEEE ARABIC LETTER WAW FINAL FORM -> U+0648 ARABIC LETTER WAW // U+FE93 ARABIC LETTER TEH MARBUTA ISOLATED FORM -> U+0629 ARABIC LETTER TEH MARBUTA ['\uFE8D\uFEDF\uFED8\uFEEC\uFEEE\uFE93', '\u0627\u0644\u0642\u0647\u0648\u0629'], - + // Combine both NFKD **AND** knocking off diacritics: // U+01C4 LATIN CAPITAL LETTER DZ WITH CARON (DŽ) -> (dz) ['DŽ', 'DZ'], ]; - + for (let [input, expected] of testCases) { it(`should normalize '${input}' to '${expected}'`, function() { assert.equal(defaultSearchTermToKey(input), expected); @@ -64,25 +64,25 @@ describe('The default searchTermToKey() function', function () { // custom searchTermToKey function. ['İstanbul', 'ISTANBUL'], ['DİYARBAKIR', 'DIYARBAKIR'], - + // "skýlos" is Greek for dog 🇬🇷🐶 // starts with an 's' and ends with an 's' // which are DIFFERENT CHARACTERS in lowercased Greek! ['σκύλος', 'ΣΚΥΛΟΣ'], ['σκυλοσ', 'ΣΚΥΛΟΣ'], - + // full-width romaji is compatibility-canonical with ASCII characters: ['aesthetic', 'AESTHETIC'], - + // U+212B ANGSTROM SIGN (Å) // U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE (Å) // and should both normalize to 'a' ['\u212B', 'A'], ['\u00C5', 'A'], - + // We should not fall for U+037E GREEK QUESTION MARK's trolling: ['\u037e', ';'], - + // Test presentational forms of Arabic: // U+FE8D ARABIC LETTER ALEF ISOLATED FORM -> U+0627 ARABIC LETTER ALEF // U+FEDF ARABIC LETTER LAM INITIAL FORM -> U+0644 ARABIC LETTER LAM @@ -91,7 +91,7 @@ describe('The default searchTermToKey() function', function () { // U+FEEE ARABIC LETTER WAW FINAL FORM -> U+0648 ARABIC LETTER WAW // U+FE93 ARABIC LETTER TEH MARBUTA ISOLATED FORM -> U+0629 ARABIC LETTER TEH MARBUTA ['\uFE8D\uFEDF\uFED8\uFEEC\uFEEE\uFE93', '\u0627\u0644\u0642\u0647\u0648\u0629'], - + // Combine both NFKD **AND** knocking off diacritics: // U+01C4 LATIN CAPITAL LETTER DZ WITH CARON (DŽ) -> (dz) ['DŽ', 'DZ'], @@ -101,8 +101,8 @@ describe('The default searchTermToKey() function', function () { // as U+0130's default handling is... not ideal in Turkish. // // Instead, we can get a simple-enough test with inverted casing. - let customCasing = function(caseToApply: CasingForm, - text: string, + let customCasing = function(caseToApply: CasingForm, + text: string, defaultApplyCasing: CasingFunction): string { switch(caseToApply) { case 'lower': @@ -110,7 +110,7 @@ describe('The default searchTermToKey() function', function () { case 'upper': return text.toLowerCase(); case 'initial': - return customCasing('upper', text.charAt(0), defaultApplyCasing) + text.substr(1); + return customCasing('upper', text.charAt(0), defaultApplyCasing) + text.substr(1); default: return text; } @@ -119,7 +119,7 @@ describe('The default searchTermToKey() function', function () { let customCasingClosure = function(caseToApply: CasingForm, text: string): string { return customCasing(caseToApply, text, defaultApplyCasing); } - + for (let [input, expected] of testCases) { it(`should normalize '${input}' to '${expected}'`, function() { assert.equal(defaultCasedSearchTermToKey(input, customCasingClosure as CasingFunction), expected); @@ -132,32 +132,32 @@ describe('The default searchTermToKey() function', function () { // "İstanbul" has a U+0130 LATIN CAPITAL LETTER I WITH DOT ABOVE. // This should be lowercased. ['İstanbul', 'istanbul'], - + // The DEFAULT function is NOT responsible for understanding the Turkish // case regarding the lowercasing of: // 'I' U+0048 LATIN CAPITAL LETTER I to 'ı' U+0131 LATIN SMALL LETTER DOTLESS I // For Turkic languages, the recommendation is to make a // custom searchTermToKey function: ['DİYARBAKIR', 'diyarbakir'], - + // "skýlos" is Greek for dog 🇬🇷🐶 // starts with an 's' and ends with an 's' // which are DIFFERENT CHARACTERS in lowercased Greek! ['σκύλος', 'σκυλος'], ['ΣΚΥΛΟΣ', 'σκυλοσ'], - + // full-width romaji is compatibility-canonical with ASCII characters: ['aesthetic', 'aesthetic'], - + // U+212B ANGSTROM SIGN (Å) // U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE (Å) // and should both normalize to 'a' ['\u212B', 'a'], ['\u00C5', 'a'], - + // We should not fall for U+037E GREEK QUESTION MARK's trolling: ['\u037e', ';'], - + // Test presentational forms of Arabic: // U+FE8D ARABIC LETTER ALEF ISOLATED FORM -> U+0627 ARABIC LETTER ALEF // U+FEDF ARABIC LETTER LAM INITIAL FORM -> U+0644 ARABIC LETTER LAM @@ -166,12 +166,12 @@ describe('The default searchTermToKey() function', function () { // U+FEEE ARABIC LETTER WAW FINAL FORM -> U+0648 ARABIC LETTER WAW // U+FE93 ARABIC LETTER TEH MARBUTA ISOLATED FORM -> U+0629 ARABIC LETTER TEH MARBUTA ['\uFE8D\uFEDF\uFED8\uFEEC\uFEEE\uFE93', '\u0627\u0644\u0642\u0647\u0648\u0629'], - + // Combine both NFKD **AND** knocking off diacritics: // U+01C4 LATIN CAPITAL LETTER DZ WITH CARON (DŽ) -> (dz) ['DŽ', 'dz'], ]; - + for (let [input, expected] of testCases) { it(`should normalize '${input}' to '${expected}'`, function() { assert.equal(defaultCasedSearchTermToKey(input, defaultApplyCasing), expected); diff --git a/developer/src/kmlmc/tests/test-error-logger.ts b/developer/src/kmc-model/test/test-error-logger.ts similarity index 86% rename from developer/src/kmlmc/tests/test-error-logger.ts rename to developer/src/kmc-model/test/test-error-logger.ts index ad0a3cbadbc..fa57d3df578 100644 --- a/developer/src/kmlmc/tests/test-error-logger.ts +++ b/developer/src/kmc-model/test/test-error-logger.ts @@ -1,6 +1,6 @@ -import { MAX_MESSAGES, KeymanCompilerError, log } from "../dist/errors" -import { LogHoarder } from "./helpers" -import { assert } from "chai" +import { MAX_MESSAGES, KeymanCompilerError, log } from "../src/model-compiler-errors.js"; +import { LogHoarder } from "./helpers/index.js"; +import { assert } from "chai"; describe('error logger', function () { beforeEach(function () { diff --git a/developer/src/kmlmc/tests/test-join-word-breaker.ts b/developer/src/kmc-model/test/test-join-word-breaker.ts similarity index 92% rename from developer/src/kmlmc/tests/test-join-word-breaker.ts rename to developer/src/kmc-model/test/test-join-word-breaker.ts index 9a2ad54e634..d06bc7a6efd 100644 --- a/developer/src/kmlmc/tests/test-join-word-breaker.ts +++ b/developer/src/kmc-model/test/test-join-word-breaker.ts @@ -1,8 +1,6 @@ -var assert = require('chai').assert; - -const defaultWordBreaker = require('../../../../common/models/wordbreakers/build').wordBreakers['default']; -import {decorateWithJoin} from '../dist/lexical-model-compiler/join-word-breaker-decorator'; - +import { assert } from "chai"; +import defaultWordBreaker from './wordbreakers/default-wordbreaker-esm.js'; +import {decorateWithJoin} from '../src/join-word-breaker-decorator.js'; describe('The join word breaker decorator', function () { it('should decorate an existing word breaker', function () { diff --git a/developer/src/kmlmc/tests/test-model-definitions.ts b/developer/src/kmc-model/test/test-model-definitions.ts similarity index 98% rename from developer/src/kmlmc/tests/test-model-definitions.ts rename to developer/src/kmc-model/test/test-model-definitions.ts index 507c3053333..297bb1a995b 100644 --- a/developer/src/kmlmc/tests/test-model-definitions.ts +++ b/developer/src/kmc-model/test/test-model-definitions.ts @@ -1,6 +1,7 @@ import 'mocha'; import { assert } from 'chai'; -import { ModelDefinitions } from '../dist/lexical-model-compiler/model-definitions'; +import { ModelDefinitions } from '../src/model-definitions.js'; +import { LexicalModelSource } from '../src/lexical-model.js'; describe('Model definition pseudoclosures', function () { describe('14.0 defaults', function() { diff --git a/developer/src/kmlmc/tests/test-override-script-defaults.ts b/developer/src/kmc-model/test/test-override-script-defaults.ts similarity index 84% rename from developer/src/kmlmc/tests/test-override-script-defaults.ts rename to developer/src/kmc-model/test/test-override-script-defaults.ts index 5f25e69a2dd..909f52c2981 100644 --- a/developer/src/kmlmc/tests/test-override-script-defaults.ts +++ b/developer/src/kmc-model/test/test-override-script-defaults.ts @@ -1,7 +1,6 @@ -var assert = require('chai').assert; - -const defaultWordBreaker = require('../../../../common/models/wordbreakers/build').wordBreakers['default']; -import {decorateWithScriptOverrides} from '../dist/lexical-model-compiler/script-overrides-decorator'; +import { assert } from "chai"; +import defaultWordBreaker from './wordbreakers/default-wordbreaker-esm.js'; +import {decorateWithScriptOverrides} from '../src/script-overrides-decorator.js'; const THIN_SPACE = "\u2009"; diff --git a/developer/src/kmlmc/tests/test-parse-wordlist.ts b/developer/src/kmc-model/test/test-parse-wordlist.ts similarity index 96% rename from developer/src/kmlmc/tests/test-parse-wordlist.ts rename to developer/src/kmc-model/test/test-parse-wordlist.ts index 3d0c7a565b1..6f5be70ca7e 100644 --- a/developer/src/kmlmc/tests/test-parse-wordlist.ts +++ b/developer/src/kmc-model/test/test-parse-wordlist.ts @@ -1,8 +1,8 @@ -import {parseWordListFromContents, parseWordListFromFilename, WordList} from '../dist/lexical-model-compiler/build-trie'; +import {parseWordListFromContents, parseWordListFromFilename, WordList} from '../src/build-trie.js'; import {assert} from 'chai'; import 'mocha'; -import { makePathToFixture, LogHoarder } from './helpers'; -import { KeymanCompilerError } from '../dist/errors'; +import { makePathToFixture, LogHoarder } from './helpers/index.js'; +import { KeymanCompilerError } from '../src/model-compiler-errors.js'; const BOM = '\ufeff'; const SENCOTEN_WORDLIST = { @@ -100,7 +100,7 @@ describe('parsing a word list', function () { parseWordListFromContents(repeatedWords, file); assert.deepEqual(repeatedWords, expected); - + assert.isTrue(this.logHoarder.hasSeenWarnings()); // hello has been seen multiple times: assert.isTrue(this.logHoarder.hasSeenCode(KeymanCompilerError.CWARN_DuplicateWordInSameFile)); diff --git a/developer/src/kmlmc/tests/test-punctuation.ts b/developer/src/kmc-model/test/test-punctuation.ts similarity index 85% rename from developer/src/kmlmc/tests/test-punctuation.ts rename to developer/src/kmc-model/test/test-punctuation.ts index 925a01bd8ee..34dbafca327 100644 --- a/developer/src/kmlmc/tests/test-punctuation.ts +++ b/developer/src/kmc-model/test/test-punctuation.ts @@ -1,10 +1,8 @@ -import LexicalModelCompiler from '../dist/lexical-model-compiler/lexical-model-compiler'; +import LexicalModelCompiler from '../src/lexical-model-compiler.js'; import {assert} from 'chai'; import 'mocha'; -import { compileModelSourceCode } from './helpers'; -import { makePathToFixture } from './helpers'; - +import { makePathToFixture, compileModelSourceCode } from './helpers/index.js'; describe('LexicalModelCompiler', function () { describe('specifying punctuation', function () { diff --git a/developer/src/kmc-model/test/tsconfig.json b/developer/src/kmc-model/test/tsconfig.json new file mode 100644 index 00000000000..fd7f672037a --- /dev/null +++ b/developer/src/kmc-model/test/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../kmc/tsconfig.kmc-base.json", + + "compilerOptions": { + "rootDir": ".", + "rootDirs": ["./", "../src/"], + "outDir": "../build/test", + "esModuleInterop": true, + "moduleResolution": "node16", + "allowSyntheticDefaultImports": true, + "baseUrl": ".", + }, + "include": [ + "**/test-*.ts", + "*.ts", + "helpers/*.ts", + "wordbreakers/*.ts" + ], + "references": [ + { "path": "../" }, + ] +} \ No newline at end of file diff --git a/developer/src/kmc-model/test/wordbreakers/README.md b/developer/src/kmc-model/test/wordbreakers/README.md new file mode 100644 index 00000000000..d204b51c6a3 --- /dev/null +++ b/developer/src/kmc-model/test/wordbreakers/README.md @@ -0,0 +1,3 @@ +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 new file mode 100644 index 00000000000..5622bf5c037 --- /dev/null +++ b/developer/src/kmc-model/test/wordbreakers/data.ts @@ -0,0 +1,1776 @@ +// 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 new file mode 100644 index 00000000000..55c0e560da6 --- /dev/null +++ b/developer/src/kmc-model/test/wordbreakers/default-wordbreaker-esm.ts @@ -0,0 +1,383 @@ +// 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]; + } diff --git a/developer/src/kmlmc/libexec/create-override-script-regexp.ts b/developer/src/kmc-model/tools/create-override-script-regexp.ts similarity index 95% rename from developer/src/kmlmc/libexec/create-override-script-regexp.ts rename to developer/src/kmc-model/tools/create-override-script-regexp.ts index 7cc262282a3..38604320583 100644 --- a/developer/src/kmlmc/libexec/create-override-script-regexp.ts +++ b/developer/src/kmc-model/tools/create-override-script-regexp.ts @@ -15,7 +15,7 @@ import {readFileSync} from "fs"; import * as path from "path"; // Where to find UnicodeData.txt and Blocks.txt -const UCD_DIR = path.join("..", "..", "resources", "standards-data", "unicode-character-database"); +const UCD_DIR = path.join("..", "..", "..", "..", "resources", "standards-data", "unicode-character-database"); const SPACELESS_SCRIPT_BLOCKS = new Set([ "Myanmar", // a.k.a., Burmese @@ -30,7 +30,7 @@ const SPACELESS_SCRIPT_BLOCKS = new Set([ let blockIter = blocks(); let block = nextBlock(); -let eligibleCharacters = []; +let eligibleCharacters: number[] = []; // @ts-ignore: TypeScript complains that it can't compile for..of over a // generator without --downlevelIteration, but it works anyway? @@ -116,7 +116,7 @@ function isLetterOrMark(category: string): boolean { } function groupCodePointsIntoRanges(characters: number[]): [number, number][] { - let ranges = []; + let ranges: [number,number][] = []; let previousCharacter = characters[0]; const candidates = characters.slice(1); diff --git a/developer/src/kmc-model/tsconfig.json b/developer/src/kmc-model/tsconfig.json new file mode 100644 index 00000000000..e48ec745f9d --- /dev/null +++ b/developer/src/kmc-model/tsconfig.json @@ -0,0 +1,17 @@ +{ + "extends": "../kmc/tsconfig.kmc-base.json", + + "compilerOptions": { + "outDir": "build/src/", + "rootDir": "src/", + "baseUrl": ".", + "allowSyntheticDefaultImports": true, + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { "path": "../../../common/web/keyman-version/tsconfig.esm.json" }, + { "path": "../../../common/models/types" }, + ] +} diff --git a/developer/src/kmc-package/Makefile b/developer/src/kmc-package/Makefile new file mode 100644 index 00000000000..02bf07dc025 --- /dev/null +++ b/developer/src/kmc-package/Makefile @@ -0,0 +1,38 @@ +# +# Keyman Developer - kmc Package Compiler Makefile +# + +!include ..\Defines.mak + +# We don't depend on configure here because kmc-keyboard does that already +build: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh build + +configure: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh configure + +clean: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh clean + +test: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh test + +# build.sh bundle must be run from shell as it requires a temp folder to be +# passed in. See inst/download.in.mak for instantiation. + +publish: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh publish + +signcode: + @rem nothing to do + +wrap-symbols: + @rem nothing to do + +test-manifest: + @rem nothing to do + +install: + @rem nothing to do + +!include ..\Target.mak diff --git a/developer/src/kmc-package/build.sh b/developer/src/kmc-package/build.sh new file mode 100755 index 00000000000..ae69a60c6b7 --- /dev/null +++ b/developer/src/kmc-package/build.sh @@ -0,0 +1,66 @@ +#!/usr/bin/env bash +# +# Compiles the kmc lexical model package compiler. +# + +# 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 + +cd "$THIS_SCRIPT_PATH" + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +builder_describe "Build Keyman kmc Package Compiler module" \ + "@/common/web/keyman-version" \ + "configure" \ + "build" \ + "clean" \ + "test" \ + "publish publish to npm" \ + "--dry-run,-n don't actually publish, just dry run" +builder_describe_outputs \ + configure /node_modules \ + build build/src/kmp-compiler.js +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action clean; then + rm -rf ./build/ ./tsconfig.tsbuildinfo + 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 + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action test; then + npm test + builder_finish_action success test +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action publish; then + . "$KEYMAN_ROOT/resources/build/npm-publish.inc.sh" + npm_publish + builder_finish_action success publish +fi diff --git a/developer/src/kmc-package/package.json b/developer/src/kmc-package/package.json new file mode 100644 index 00000000000..4308867ee0e --- /dev/null +++ b/developer/src/kmc-package/package.json @@ -0,0 +1,53 @@ +{ + "name": "@keymanapp/kmc-package", + "description": "Keyman Developer package compiler", + "keywords": [ + "keyboard", + "keyman", + "unicode" + ], + "type": "module", + "exports": { + ".": "./build/src/kmp-compiler.js", + "./kmp-json-file": "./build/src/kmp-json-file.js" + }, + "scripts": { + "build": "tsc -b", + "test": "cd test && tsc -b && cd .. && c8 --reporter=lcov --reporter=text mocha", + "coverage": "npm test", + "prepublishOnly": "npm run build" + }, + "author": "Marc Durdin (https://github.com/mcdurdin)", + "contributors": [ + "Eddie Antonio Santos ", + "Joshua Horton" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/keymanapp/keyman/issues" + }, + "dependencies": { + "jszip": "^3.7.0" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "^4.5.4" + }, + "mocha": { + "spec": "build/test/**/test-*.js", + "require": [ + "source-map-support/register" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" + } +} diff --git a/developer/src/kmc-package/src/kmp-compiler.ts b/developer/src/kmc-package/src/kmp-compiler.ts new file mode 100644 index 00000000000..6ee68ddbb7b --- /dev/null +++ b/developer/src/kmc-package/src/kmp-compiler.ts @@ -0,0 +1,271 @@ +/// +/// + +import * as fs from 'fs'; +import * as path from 'path'; +import * as xml2js from 'xml2js'; +import JSZip from 'jszip'; +import KEYMAN_VERSION from "@keymanapp/keyman-version/keyman-version.mjs"; + +const FILEVERSION_KMP_JSON = '12.0'; + +export default class KmpCompiler { + + public transformKpsToKmpObject(kpsString: string, kpsPath: string): KmpJsonFile { + + // Load the KPS data from XML as JS structured data. + + let kpsPackage = (() => { + let a: KpsPackage; + let parser = new xml2js.Parser({ + tagNameProcessors: [xml2js.processors.firstCharLowerCase], + explicitArray: false + }); + parser.parseString(kpsString, (e: unknown, r: unknown) => { a = r as KpsPackage }); + return a; + })(); + + let kps: KpsFile = kpsPackage.package; + + // + // To convert to kmp.json, we need to: + // + // 1. Unwrap arrays (and convert to array where single object) + // 2. Fix casing on `iD` + // 3. Rewrap info, keyboard.languages, lexicalModel.languages, startMenu.items elements + // 4. Remove options.followKeyboardVersion, file.fileType + // 5. Convert file.copyLocation to a Number + // 6. Filenames need to be basenames (but this comes after processing) + // + + // Start to construct the kmp.json file from the .kps file + + let kmp: KmpJsonFile = { + system: { + fileVersion: FILEVERSION_KMP_JSON, + keymanDeveloperVersion: KEYMAN_VERSION.VERSION + }, + options: {} + }; + + // + // Fill in additional fields + // + + let keys: [keyof KpsFileOptions, keyof KmpJsonFileOptions][] = [ + ['executeProgram','executeProgram'], + ['graphicFile', 'graphicFile'], + ['msiFileName','msiFilename'], + ['msiOptions', 'msiOptions'], + ['readMeFile', 'readmeFile'] + ]; + for (let [src,dst] of keys) { + if (kps.options[src]) { + kmp.options[dst] = kps.options[src]; + } + } + + // + // Add basic metadata + // + + if(kps.info) { + kmp.info = this.kpsInfoToKmpInfo(kps.info); + } + + // FollowKeyboardVersion support + + if(kps.options?.followKeyboardVersion !== undefined) { + kmp.info.version = { + description: this.extractKeyboardVersionFromKmx(kpsPath, kps) + }; + } + + // + // Add file metadata + // + + if(kps.files && kps.files.file) { + kmp.files = this.arrayWrap(kps.files.file).map((file: KpsFileContentFile) => { + return { + name: file.name, + description: file.description, + copyLocation: parseInt(file.copyLocation, 10) || undefined + // note: we don't emit fileType as that is not permitted in kmp.json + }; + }); + } + kmp.files = kmp.files ?? []; + + // Add the standard kmp.json self-referential to match existing implementations + kmp.files.push({ + name: "kmp.json", + description: "Package information (JSON)" + }); + + // + // Add keyboard metadata + // + + if(kps.keyboards && kps.keyboards.keyboard) { + kmp.keyboards = this.arrayWrap(kps.keyboards.keyboard).map((keyboard: KpsFileKeyboard) => { + return { + displayFont: keyboard.displayFont ? path.basename(keyboard.displayFont.replaceAll('\\', '/')) : undefined, + oskFont: keyboard.oSKFont ? path.basename(keyboard.oSKFont.replaceAll('\\', '/')) : undefined, + name:keyboard.name, + id:keyboard.iD, + version:keyboard.version, + languages: this.kpsLanguagesToKmpLanguages(this.arrayWrap(keyboard.languages.language) as KpsFileLanguage[]) + }; + }); + } + + // + // Add lexical-model metadata + // + + if(kps.lexicalModels && kps.lexicalModels.lexicalModel) { + kmp.lexicalModels = this.arrayWrap(kps.lexicalModels.lexicalModel).map((model: KpsFileLexicalModel) => { + return { name:model.name, id:model.iD, languages: this.kpsLanguagesToKmpLanguages(this.arrayWrap(model.languages.language) as KpsFileLanguage[]) } + }); + } + + // + // Add Windows Start Menu metadata + // + + if(kps.startMenu && kps.startMenu.items) { + kmp.startMenu = {}; + if(kps.startMenu.addUninstallEntry) kmp.startMenu.addUninstallEntry = kps.startMenu.addUninstallEntry === ''; + 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); + } + + // + // Add translation strings + // + + if(kps.strings && kps.strings.string) { + kmp.strings = this.arrayWrap(kps.strings.string); + } + + kmp = this.stripUndefined(kmp) as KmpJsonFile; + + return kmp; + } + + // Helper functions + + private kpsInfoToKmpInfo(info: KpsFileInfo): KmpJsonFileInfo { + let ni: KmpJsonFileInfo = {}; + + const keys: [(keyof KpsFileInfo), (keyof KmpJsonFileInfo)][] = [ + ['author','author'], + ['copyright','copyright'], + ['name','name'], + ['version','version'], + ['webSite','website'] + ]; + + 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; + } + } + + return ni; + }; + + private arrayWrap(a: unknown) { + if (Array.isArray(a)) { + return a; + } + return [a]; + }; + + private kpsLanguagesToKmpLanguages(language: KpsFileLanguage[]): KmpJsonFileLanguage[] { + return language.map((element) => { return { name: element._, id: element.$.ID } }); + }; + + private extractKeyboardVersionFromKmx(kpsPath: string, kps: KpsFile) { + // TODO-LDML: #7340 this is incomplete; we need to read from first .kmx + // When we do this, we'll need to update fixtures to have the correct + // version string also + if(!kps.keyboards || !kps.keyboards.keyboard) { + // We don't read from the kps metadata because we want the keyboard + // version data here; + // TODO: currently model files don't support follow-version? + return '0.0.0'; + } + let k: KpsFileKeyboard[] = this.arrayWrap(kps.keyboards.keyboard); + return k?.[0]?.version ?? '0.0.0'; + } + + private stripUndefined(o: any) { + for(const key in o) { + if(o[key] === undefined) { + delete o[key]; + } else if(typeof o[key] == 'object') { + o[key] = this.stripUndefined(o[key]); + } + } + return o; + } + + /** + * Returns a Promise to the serialized data which can then be written to a .kmp file. + * + * @param kpsFilename - Filename of the kps, not read, used only for calculating relative paths + * @param kmpJsonData - The kmp.json Object + */ + public buildKmpFile(kpsFilename: string, kmpJsonData: KmpJsonFile): Promise { + const zip = JSZip(); + + const kmpJsonFileName = 'kmp.json'; + + const basePath = path.dirname(kpsFilename); + + // Make a copy of kmpJsonData, as we mutate paths for writing + const data: KmpJsonFile = JSON.parse(JSON.stringify(kmpJsonData)); + if(!data.files) { + data.files = []; + } + + data.files.forEach(function(value) { + // Get the path of the file + let filename = value.name; + + // We add this separately after zipping all other files + if(filename == 'kmp.json') { + return; + } + + if(path.isAbsolute(value.name)) { + // absolute paths are not very cross-platform compatible -- we are going to have trouble + // with path separators and roots + // TODO: emit a warning + } else { + // Transform separators to platform separators -- kps files may use + // either / or \, although older kps files were always \. + if(path.sep == '/') { + filename = filename.replace(/\\/g, '/'); + } else { + filename = filename.replace(/\//g, '\\'); + } + filename = path.resolve(basePath, filename); + } + const basename = path.basename(filename); + let data = fs.readFileSync(filename); + zip.file(basename, data); + + // Remove path data from files before JSON save + value.name = basename; + }); + + zip.file(kmpJsonFileName, JSON.stringify(data, null, 2)); + + // Generate kmp file + return zip.generateAsync({type: 'binarystring', compression:'DEFLATE'}); + } +} diff --git a/developer/src/kmlmc/source/package-compiler/kmp-json-file.ts b/developer/src/kmc-package/src/kmp-json-file.ts similarity index 100% rename from developer/src/kmlmc/source/package-compiler/kmp-json-file.ts rename to developer/src/kmc-package/src/kmp-json-file.ts diff --git a/developer/src/kmlmc/source/package-compiler/kps-file.ts b/developer/src/kmc-package/src/kps-file.ts similarity index 96% rename from developer/src/kmlmc/source/package-compiler/kps-file.ts rename to developer/src/kmc-package/src/kps-file.ts index ab2774f3aaf..850c64c5508 100644 --- a/developer/src/kmlmc/source/package-compiler/kps-file.ts +++ b/developer/src/kmc-package/src/kps-file.ts @@ -38,10 +38,10 @@ interface KpsFileSystem { interface KpsFileOptions { followKeyboardVersion?: string; - readmeFile?: string; + readMeFile?: string; graphicFile?: string; executeProgram?: string; - msiFilename?: string; + msiFileName?: string; msiOptions?: string; } @@ -49,7 +49,7 @@ interface KpsFileInfo { name?: KpsFileInfoItem; copyright?: KpsFileInfoItem; author?: KpsFileInfoItem; - website?: KpsFileInfoItem; + webSite?: KpsFileInfoItem; version?: KpsFileInfoItem; } @@ -92,7 +92,7 @@ interface KpsFileKeyboard { name: string; iD: string; version: string; - oskFont?: string; + oSKFont?: string; displayFont?: string; rtl?: boolean; languages?: KpsFileLanguages; diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.js b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.js similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.js rename to developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.js diff --git a/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.intermediate.json b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.intermediate.json new file mode 100644 index 00000000000..97ef6967b73 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.intermediate.json @@ -0,0 +1,48 @@ +{ + "system": { + "keymanDeveloperVersion": "12.0.1500.0", + "fileVersion": "12.0" + }, + "options": {}, + "info": { + "author": { + "description": "Eddie Antonio Santos", + "url": "mailto:Eddie.Santos@nrc-cnrc.gc.ca" + }, + "copyright": { + "description": "© 2019 National Research Council Canada" + }, + "name": { + "description": "SENĆOŦEN (Saanich Dialect) Lexical Model" + }, + "version": { + "description": "0.0.0" + } + }, + "files": [ + { + "name": "example.qaa.sencoten.model.js", + "description": "Lexical model example.qaa.sencoten.model.js" + }, + { + "description": "Package information (JSON)", + "name": "kmp.json" + } + ], + "lexicalModels": [ + { + "name": "SENĆOŦEN dictionary", + "id": "example.qaa.sencoten", + "languages": [ + { + "name": "North Straits Salish", + "id": "qaa" + }, + { + "name": "SENĆOŦEN", + "id": "qaa-Latn" + } + ] + } + ] +} diff --git a/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.json b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.json new file mode 100644 index 00000000000..dacbc3ee67e --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.json @@ -0,0 +1,48 @@ +{ + "system": { + "keymanDeveloperVersion": "12.0.1500.0", + "fileVersion": "12.0" + }, + "options": {}, + "info": { + "author": { + "description": "Eddie Antonio Santos", + "url": "mailto:Eddie.Santos@nrc-cnrc.gc.ca" + }, + "copyright": { + "description": "© 2019 National Research Council Canada" + }, + "name": { + "description": "SENĆOŦEN (Saanich Dialect) Lexical Model" + }, + "version": { + "description": "0.0.0" + } + }, + "files": [ + { + "name": "..\\build\\example.qaa.sencoten.model.js", + "description": "Lexical model example.qaa.sencoten.model.js" + }, + { + "name": "kmp.json", + "description": "Package information (JSON)" + } + ], + "lexicalModels": [ + { + "name": "SENĆOŦEN dictionary", + "id": "example.qaa.sencoten", + "languages": [ + { + "name": "North Straits Salish", + "id": "qaa" + }, + { + "name": "SENĆOŦEN", + "id": "qaa-Latn" + } + ] + } + ] +} \ No newline at end of file diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.zipped.json b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.zipped.json similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.zipped.json rename to developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kmp.zipped.json diff --git a/developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kps b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kps similarity index 100% rename from developer/src/kmlmc/tests/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kps rename to developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.kps diff --git a/developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.ts b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.ts similarity index 100% rename from developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.ts rename to developer/src/kmc-package/test/fixtures/example.qaa.sencoten/example.qaa.sencoten.model.ts diff --git a/developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/source/wordlist.tsv b/developer/src/kmc-package/test/fixtures/example.qaa.sencoten/wordlist.tsv similarity index 100% rename from developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/source/wordlist.tsv rename to developer/src/kmc-package/test/fixtures/example.qaa.sencoten/wordlist.tsv diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/build/khmer_angkor.js b/developer/src/kmc-package/test/fixtures/khmer_angkor/build/khmer_angkor.js new file mode 100644 index 00000000000..4ee51e4d3e6 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/build/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/khmer_angkor/build/khmer_angkor.kmx b/developer/src/kmc-package/test/fixtures/khmer_angkor/build/khmer_angkor.kmx new file mode 100644 index 00000000000..c3d0289f486 Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/build/khmer_angkor.kmx differ diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/build/khmer_angkor.kvk b/developer/src/kmc-package/test/fixtures/khmer_angkor/build/khmer_angkor.kvk new file mode 100644 index 00000000000..c64b3a5a544 Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/build/khmer_angkor.kvk differ 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 new file mode 100644 index 00000000000..d5aa3271db3 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/ref/kmp.json @@ -0,0 +1,110 @@ +{ + "system": { + "keymanDeveloperVersion": "16.0.60.0", + "fileVersion": "12.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" + } + }, + "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.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" + } + ] + } + ] +} diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/busrakbd/khmer_busra_kbd.ttf b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/busrakbd/khmer_busra_kbd.ttf new file mode 100644 index 00000000000..2169a408930 Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/busrakbd/khmer_busra_kbd.ttf differ diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/FONTLOG.txt b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/FONTLOG.txt new file mode 100644 index 00000000000..7829f79efc4 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/FONTLOG.txt @@ -0,0 +1,123 @@ +FONTLOG +The Mondulkiri Font Family +Khmer Unicode Fonts +======================== + + +This file provides detailed information on the Khmer Mondulkiri family of fonts. +This information should be distributed along with the Mondulkiri fonts and +any derivative works. + +Basic Font Information +---------------------- + +The Mondulkiri fonts provide Unicode support for the Khmer script. +"Mondulkiri" is the name of a province in north-eastern Cambodia, +These fonts are provided free of charge. Previous versions of the fonts have +been available since 2003 but are now available on the SIL website. + +The following fonts from the Mondulkiri typeface family are included in this +release: + + * Khmer Mondulkiri Regular + * Khmer Mondulkiri Bold + * Khmer Mondulkiri Italic + * Khmer Mondulkiri Bold Italic + * Khmer Busra Regular + * Khmer Busra Bold + * Khmer Busra Italic + * Khmer Busra Bold Italic + +ChangeLog +--------- +(This should list both major and minor changes, most recent first.) +8 September 2014 (Diethelm Kanjahn) Mondulkiri Font Family version 7.100 +- Mondulkiri changes: + * The hinting has been improved, the difference between regular and bold + is visible on screen in much smaller font sizes than before. + * A few bugs were addressed: the disappearing of glyphs on Mac in certain + strings that do not occur in correctly typed text, a positioning problem + in some Tampuan words on Mac, one syllable in Bunong text on Windows/Linux. + * Minor changes in diacritic placement, mostly on Mac. Coeng Kho and coeng + Ttha were modified. + * Glyphs were added - mostly in the phonetic symbols section. The shaping + tables for Mac were streamlined and are now the same as for the Busra font. +- Busra changes: + * The fonts now have AAT tables so that they work on Mac OS X + * many glyphs were added, the glyph range is the same as in Mondulkiri 7.100 + * many glyph shapes and diacritic placements were improved slightly + * the ligatures of some consonants with the sra-a, sra-oo and sra-ou + have changed and are now the same as in Mondulkiri. + * hinting has been improved + * most other changes from Mondulkiri 5.300 to 5.513 are now also in Busra 7.100 + * The descender value was changed which may affect line spacing. + +31 October 2012 (Diethelm Kanjahn) Mondulkiri Font Family version 5.300 +- Split Khmer Mondulkiri fonts from older fonts release +- Support for Apple's AAT rendering system (tested in OS X 10.6.8) as well as + Microsoft OpenType tables. +- Support for typographical features to be used under OS X with software that + does support them (like Pages or TextEdit, but apparently not MS Word or + LibreOffice). +- Support for 'Stylistic Sets' that provide similar options in OpenType as the + typographical features in OS X. These Stylistic Sets work in Adobe software + (tested with CS5 and 5.5 on PC), but not in MS Word. MS Word 2010 does support + Stylistic Sets, but apparently only for Latin scripts. +- Support for Khmer Unicode in Adobe CS5 and CS5.5. Two of the Stylistic Sets + address the remaining problems. Please refer to the documentation on stylistic + sets. +- Many Latin script characters have been added. The bold fonts now have also + bold Latin characters. +- Some of the vowel-a ligatures have been changed (e.g. Cho-AA). +- Many characters have been slightly modified. Most significantly the hooks or + hair of some coengs have been removed(e.g. coeng-Kho, coeng-Ha, coeng-Qa). +- The positioning of many characters was modified. +- Many other smaller modifications of glyphs and rules. +- To prevent register shifters from being rendered as subscript + zero-width-non-joiner has now to be inserted before the register shifter in + accordance with The Unicode Standard. Also zero-width-joiner has to be in this + position now to force the alternative rendering. +- Increased the descender by a small amount so that second level elements are no + longer cut off. +- For users of the Tampuan language: coeng-vo after coeng-ro and shifting of + samyuk before reahmuk are now enabled by default. The Tampuan option now only + covers the width of space and the permission to use samyuk after reahmuk. +- The support for the use of vowels and coengs with independent vowels in OS X + is limited. If there is a real need for improvement please contact the font + designer. +- Changed position of quotes (U+2018-9, 201C-D) and mirrored U+201C. + +09 November 2010 (Diethelm Kanjahn) Mondulkiri Font Family version 5.300 +- First version released under the SIL Open Font License +- First version released on http://scripts.sil.org +- Added fonts: Ratanakiri, all italic typefaces +- Renamed fonts: Busra and Oureang +- Major OpenType code revisions +- Corrections of outlines to comply with TTF conventions +- Many small edits of the outlines +- Outlines are now TrueType outlines +- Improved hinting +- Added characters +- Size of Khmer base characters now equivalent to Roman capital letters. + +2005-09-16 (Diethelm Kanjahn) Mondulkiri Font Family version 1.1 + - OpenType and Graphite test fonts + +2003-04-12 (Diethelm Kanjahn) Mondulkiri Font Family version 0.9 +- first public release as legacy fonts + + +Acknowledgements +---------------- + +N: Diethelm Kanjahn +E: fonts@sil.org +W: http://scripts.sil.org/ +D: Designer and font engineer + +For more information please visit The Mondulkiri Font Family page on SIL +International's +Computers and Writing systems website: +http://scripts.sil.org/Mondulkiri + +Support through the website: http://scripts.sil.org/Support diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/Mondulkiri-R.ttf b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/Mondulkiri-R.ttf new file mode 100644 index 00000000000..e109ed60287 Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/Mondulkiri-R.ttf differ diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/OFL-FAQ.txt b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/OFL-FAQ.txt new file mode 100644 index 00000000000..0893d7cf52b --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/OFL-FAQ.txt @@ -0,0 +1,425 @@ +OFL FAQ - Frequently Asked Questions about the SIL Open Font License (OFL) +Version 1.1-update3 - Sept 2013 +(See http://scripts.sil.org/OFL for updates) + + +CONTENTS OF THIS FAQ +1 USING AND DISTRIBUTING FONTS LICENSED UNDER THE OFL +2 USING OFL FONTS FOR WEB PAGES AND ONLINE WEB FONT SERVICES +3 MODIFYING OFL-LICENSED FONTS +4 LICENSING YOUR ORIGINAL FONTS UNDER THE OFL +5 CHOOSING RESERVED FONT NAMES +6 ABOUT THE FONTLOG +7 MAKING CONTRIBUTIONS TO OFL PROJECTS +8 ABOUT THE LICENSE ITSELF +9 ABOUT SIL INTERNATIONAL +APPENDIX A - FONTLOG EXAMPLE + +1 USING AND DISTRIBUTING FONTS LICENSED UNDER THE OFL + +1.1 Can I use the fonts for a book or other print publication, to create logos or other graphics or even to manufacture objects based on their outlines? +Yes. You are very welcome to do so. Authors of fonts released under the OFL allow you to use their font software as such for any kind of design work. No additional license or permission is required, unlike with some other licenses. Some examples of these uses are: logos, posters, business cards, stationery, video titling, signage, t-shirts, personalised fabric, 3D-printed/laser-cut shapes, sculptures, rubber stamps, cookie cutters and lead type. + +1.1.1 Does that restrict the license or distribution of that artwork? +No. You remain the author and copyright holder of that newly derived graphic or object. You are simply using an open font in the design process. It is only when you redistribute, bundle or modify the font itself that other conditions of the license have to be respected (see below for more details). + +1.1.2 Is any kind of acknowledgement required? +No. Font authors may appreciate being mentioned in your artwork's acknowledgements alongside the name of the font, possibly with a link to their website, but that is not required. + +1.2 Can the fonts be included with Free/Libre and Open Source Software collections such as GNU/Linux and BSD distributions and repositories? +Yes! Fonts licensed under the OFL can be freely included alongside other software under FLOSS (Free/Libre and Open Source Software) licenses. Since fonts are typically aggregated with, not merged into, existing software, there is little need to be concerned about incompatibility with existing software licenses. You may also repackage the fonts and the accompanying components in a .rpm or .deb package (or other similar package formats or installers) and include them in distribution CD/DVDs and online repositories. (Also see section 5.9 about rebuilding from source.) + +1.3 I want to distribute the fonts with my program. Does this mean my program also has to be Free/Libre and Open Source Software? +No. Only the portions based on the Font Software are required to be released under the OFL. The intent of the license is to allow aggregation or bundling with software under restricted licensing as well. + +1.4 Can I sell a software package that includes these fonts? +Yes, you can do this with both the Original Version and a Modified Version of the fonts. Examples of bundling made possible by the OFL would include: word processors, design and publishing applications, training and educational software, games and entertainment software, mobile device applications, etc. + +1.5 Can I include the fonts on a CD of freeware or commercial fonts? +Yes, as long some other font or software is also on the disk, so the OFL font is not sold by itself. + +1.6 Why won't the OFL let me sell the fonts alone? +The intent is to keep people from making money by simply redistributing the fonts. The only people who ought to profit directly from the fonts should be the original authors, and those authors have kindly given up potential direct income to distribute their fonts under the OFL. Please honour and respect their contribution! + +1.7 What about sharing OFL fonts with friends on a CD, DVD or USB stick? +You are very welcome to share open fonts with friends, family and colleagues through removable media. Just remember to include the full font package, including any copyright notices and licensing information as available in OFL.txt. In the case where you sell the font, it has to come bundled with software. + +1.8 Can I host the fonts on a web site for others to use? +Yes, as long as you make the full font package available. In most cases it may be best to point users to the main site that distributes the Original Version so they always get the most recent stable and complete version. See also discussion of web fonts in Section 2. + +1.9 Can I host the fonts on a server for use over our internal network? +Yes. If the fonts are transferred from the server to the client computer by means that allow them to be used even if the computer is no longer attached to the network, the full package (copyright notices, licensing information, etc.) should be included. + +1.10 Does the full OFL license text always need to accompany the font? +The only situation in which an OFL font can be distributed without the text of the OFL (either in a separate file or in font metadata), is when a font is embedded in a document or bundled within a program. In the case of metadata included within a font, it is legally sufficient to include only a link to the text of the OFL on http://scripts.sil.org/OFL, but we strongly recommend against this. Most modern font formats include metadata fields that will accept the full OFL text, and full inclusion increases the likelihood that users will understand and properly apply the license. + +1.11 What do you mean by 'embedding'? How does that differ from other means of distribution? +By 'embedding' we mean inclusion of the font in a document or file in a way that makes extraction (and redistribution) difficult or clearly discouraged. In many cases the names of embedded fonts might also not be obvious to those reading the document, the font data format might be altered, and only a subset of the font - only the glyphs required for the text - might be included. Any other means of delivering a font to another person is considered 'distribution', and needs to be accompanied by any copyright notices and licensing information available in OFL.txt. + +1.12 So can I embed OFL fonts in my document? +Yes, either in full or a subset. The restrictions regarding font modification and redistribution do not apply, as the font is not intended for use outside the document. + +1.13 Does embedding alter the license of the document itself? +No. Referencing or embedding an OFL font in any document does not change the license of the document itself. The requirement for fonts to remain under the OFL does not apply to any document created using the fonts and their derivatives. Similarly, creating any kind of graphic using a font under OFL does not make the resulting artwork subject to the OFL. + +1.14 If OFL fonts are extracted from a document in which they are embedded (such as a PDF file), what can be done with them? Is this a risk to author(s)? +The few utilities that can extract fonts embedded in a PDF will typically output limited amounts of outlines - not a complete font. To create a working font from this method is much more difficult and time consuming than finding the source of the original OFL font. So there is little chance that an OFL font would be extracted and redistributed inappropriately through this method. Even so, copyright laws address any misrepresentation of authorship. All Font Software released under the OFL and marked as such by the author(s) is intended to remain under this license regardless of the distribution method, and cannot be redistributed under any other license. We strongly discourage any font extraction - we recommend directly using the font sources instead - but if you extract font outlines from a document, please be considerate: respect the work of the author(s) and the licensing model. + +1.15 What about distributing fonts with a document? Within a compressed folder structure? Is it distribution, bundling or embedding? +Certain document formats may allow the inclusion of an unmodified font within their file structure which may consist of a compressed folder containing the various resources forming the document (such as pictures and thumbnails). Including fonts within such a structure is understood as being different from embedding but rather similar to bundling (or mere aggregation) which the license explicitly allows. In this case the font is conveyed unchanged whereas embedding a font usually transforms it from the original format. The OFL does not allow anyone to extract the font from such a structure to then redistribute it under another license. The explicit permission to redistribute and embed does not cancel the requirement for the Font Software to remain under the license chosen by its author(s). Even if the font travels inside the document as one of its assets, it should not lose its authorship information and licensing. + +1.16 What about ebooks shipping with open fonts? +The requirements differ depending on whether the fonts are linked, embedded or distributed (bundled or aggregated). Some ebook formats use web technologies to do font linking via @font-face, others are designed for font embedding, some use fonts distributed with the document or reading software, and a few rely solely on the fonts already present on the target system. The license requirements depend on the type of inclusion as discussed in 1.15. + +1.17 Can Font Software released under the OFL be subject to URL-based access restrictions methods or DRM (Digital Rights Management) mechanisms? +Yes, but these issues are out-of-scope for the OFL. The license itself neither encourages their use nor prohibits them since such mechanisms are not implemented in the components of the Font Software but through external software. Such restrictions are put in place for many different purposes corresponding to various usage scenarios. One common example is to limit potentially dangerous cross-site scripting attacks. However, in the spirit of libre/open fonts and unrestricted writing systems, we strongly encourage open sharing and reuse of OFL fonts, and the establishment of an environment where such restrictions are unnecessary. Note that whether you wish to use such mechanisms or you prefer not to, you must still abide by the rules set forth by the OFL when using fonts released by their authors under this license. Derivative fonts must be licensed under the OFL, even if they are part of a service for which you charge fees and/or for which access to source code is restricted. You may not sell the fonts on their own - they must be part of a larger software package, bundle or subscription plan. For example, even if the OFL font is distributed in a software package or via an online service using a DRM mechanism, the user would still have the right to extract that font, use, study, modify and redistribute it under the OFL. + +1.18 I've come across a font released under the OFL. How can I easily get more information about the Original Version? How can I know where it stands compared to the Original Version or other Modified Versions? +Consult the copyright statement(s) in the license for ways to contact the original authors. Consult the FONTLOG (see section 6 for more details and examples) for information on how the font differs from the Original Version, and get in touch with the various contributors via the information in the acknowledgement section. Please consider using the Original Versions of the fonts whenever possible. + +1.19 What do you mean in condition 4 of the OFL's permissions and conditions? Can you provide examples of abusive promotion / endorsement / advertisement vs. normal acknowledgement? +The intent is that the goodwill and reputation of the author(s) should not be used in a way that makes it sound like the original author(s) endorse or approve of a specific Modified Version or software bundle. For example, it would not be right to advertise a word processor by naming the author(s) in a listing of software features, or to promote a Modified Version on a web site by saying "designed by ...". However, it would be appropriate to acknowledge the author(s) if your software package has a list of people who deserve thanks. We realize that this can seem to be a grey area, but the standard used to judge an acknowledgement is that if the acknowledgement benefits the author(s) it is allowed, but if it primarily benefits other parties, or could reflect poorly on the author(s), then it is not. + +1.20 I'm writing a small app for mobile platforms, do I need to include the whole package? +If you bundle a font under the OFL with your mobile app you must comply with the terms of the license. At a minimum you must include the copyright statement, the license notice and the license text. A mention of this information in your About box or Changelog, with a link to where the font package is from, is good practice, and the extra space needed to carry these items is very small. You do not, however, need to include the full contents of the font package - only the fonts you use and the copyright and license that apply to them. For example, if you only use the regular weight in your app, you do not need to include the italic and bold versions. + +1.21 What about including OFL fonts by default in my firmware or dedicated operating system? +Many such systems are restricted and turned into appliances so that users cannot study or modify them. Using open fonts to increase quality and language coverage is a great idea, but you need to be aware that if there is a way for users to extract fonts you cannot legally prevent them from doing that. The fonts themselves, including any changes you make to them, must be distributed under the OFL even if your firmware has a more restrictive license. If you do transform the fonts and change their formats when you include them in your firmware you must respect any names reserved by the font authors via the RFN mechanism and pick your own font name. Alternatively if you directly add a font under the OFL to the font folder of your firmware without modifying or optimizing it you are simply bundling the font like with any other software collection, and do not need to make any further changes. + +1.22 Can I make and publish CMS themes or templates that use OFL fonts? Can I include the fonts themselves in the themes or templates? Can I sell the whole package? +Yes, you are very welcome to integrate open fonts into themes and templates for your preferred CMS and make them more widely available. Remember that you can only sell the fonts and your CMS add-on as part of a software bundle. (See 1.4 for details and examples about selling bundles). + +1.23 Can OFL fonts be included in services that deliver fonts to the desktop from remote repositories? Even if they contain both OFL and non-OFL fonts? +Yes. Some foundries have set up services to deliver fonts to subscribers directly to desktops from their online repositories; similarly, plugins are available to preview and use fonts directly in your design tool or publishing suite. These services may mix open and restricted fonts in the same channel, however they should make a clear distinction between them to users. These services should also not hinder users (such as through DRM or obfuscation mechanisms) from extracting and using the OFL fonts in other environments, or continuing to use OFL fonts after subscription terms have ended, as those uses are specifically allowed by the OFL. + +1.24 Can services that provide or distribute OFL fonts restrict my use of them? +No. The terms of use of such services cannot replace or restrict the terms of the OFL, as that would be the same as distributing the fonts under a different license, which is not allowed. You are still entitled to use, modify and redistribute them as the original authors have intended outside of the sole control of that particular distribution channel. Note, however, that the fonts provided by these services may differ from the Original Versions. + + +2 USING OFL FONTS FOR WEBPAGES AND ONLINE WEB FONT SERVICES + +NOTE: This section often refers to a separate paper on 'Web Fonts & RFNs'. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs + +2.1 Can I make webpages using these fonts? +Yes! Go ahead! Using CSS (Cascading Style Sheets) is recommended. Your three best options are: +- referring directly in your stylesheet to open fonts which may be available on the user's system +- providing links to download the full package of the font - either from your own website or from elsewhere - so users can install it themselves +- using @font-face to distribute the font directly to browsers. This is recommended and explicitly allowed by the licensing model because it is distribution. The font file itself is distributed with other components of the webpage. It is not embedded in the webpage but referenced through a web address which will cause the browser to retrieve and use the corresponding font to render the webpage (see 1.11 and 1.15 for details related to embedding fonts into documents). As you take advantage of the @font-face cross-platform standard, be aware that web fonts are often tuned for a web environment and not intended for installation and use outside a browser. The reasons in favour of using web fonts are to allow design of dynamic text elements instead of static graphics, to make it easier for content to be localized and translated, indexed and searched, and all this with cross-platform open standards without depending on restricted extensions or plugins. You should check the CSS cascade (the order in which fonts are being called or delivered to your users) when testing. + +2.2 Can I make and use WOFF (Web Open Font Format) versions of OFL fonts? +Yes, but you need to be careful. A change in font format normally is considered modification, and Reserved Font Names (RFNs) cannot be used. Because of the design of the WOFF format, however, it is possible to create a WOFF version that is not considered modification, and so would not require a name change. You are allowed to create, use and distribute a WOFF version of an OFL font without changing the font name, but only if: + +- the original font data remains unchanged except for WOFF compression, and +- WOFF-specific metadata is either omitted altogether or present and includes, unaltered, the contents of all equivalent metadata in the original font. + +If the original font data or metadata is changed, or the WOFF-specific metadata is incomplete, the font must be considered a Modified Version, the OFL restrictions would apply and the name of the font must be changed: any RFNs cannot be used and copyright notices and licensing information must be included and cannot be deleted or modified. You must come up with a unique name - we recommend one corresponding to your domain or your particular web application. Be aware that only the original author(s) can use RFNs. This is to prevent collisions between a derivative tuned to your audience and the original upstream version and so to reduce confusion. + +Please note that most WOFF conversion tools and online services do not meet the two requirements listed above, and so their output must be considered a Modified Version. So be very careful and check to be sure that the tool or service you're using is compressing unchanged data and completely and accurately reflecting the original font metadata. + +2.3 What about other web font formats such as EOT/EOTLite/CWT/etc.? +In most cases these formats alter the original font data more than WOFF, and do not completely support appropriate metadata, so their use must be considered modification and RFNs may not be used. However, there may be certain formats or usage scenarios that may allow the use of RFNs. See http://scripts.sil.org/OFL_web_fonts_and_RFNs + +2.4 Can I make OFL fonts available through web font online services? +Yes, you are welcome to include OFL fonts in online web font services as long as you properly meet all the conditions of the license. The origin and open status of the font should be clear among the other fonts you are hosting. Authorship, copyright notices and license information must be sufficiently visible to your users or subscribers so they know where the font comes from and the rights granted by the author(s). Make sure the font file contains the needed copyright notice(s) and licensing information in its metadata. Please double-check the accuracy of every field to prevent contradictory information. Other font formats, including EOT/EOTLite/CWT and superior alternatives like WOFF, already provide fields for this information. Remember that if you modify the font within your library or convert it to another format for any reason the OFL restrictions apply and you need to change the names accordingly. Please respect the author's wishes as expressed in the OFL and do not misrepresent original designers and their work. Don't lump quality open fonts together with dubious freeware or public domain fonts. Consider how you can best work with the original designers and foundries, support their efforts and generate goodwill that will benefit your service. (See 1.17 for details related to URL-based access restrictions methods or DRM mechanisms). + +2.5 Some web font formats and services provide ways of "optimizing" the font for a particular website or web application; is that allowed? +Yes, it is permitted, but remember that these optimized versions are Modified Versions and so must follow OFL requirements like appropriate renaming. Also you need to bear in mind the other important parameters beyond compression, speed and responsiveness: you need to consider the audience of your particular website or web application, as choosing some optimization parameters may turn out to be less than ideal for them. Subsetting by removing certain glyphs or features may seriously limit functionality of the font in various languages that your users expect. It may also introduce degradation of quality in the rendering or specific bugs on the various target platforms compared to the original font from upstream. In other words, remember that one person's optimized font may be another person's missing feature. Various advanced typographic features (OpenType, Graphite or AAT) are also available through CSS and may provide the desired effects without the need to modify the font. + +2.6 Is subsetting a web font considered modification? +Yes. Removing any parts of the font when delivering a web font to a browser, including unused glyphs and smart font code, is considered modification. This is permitted by the OFL but would not normally allow the use of RFNs. Some newer subsetting technologies may be able to subset in a way that allows users to effectively have access to the complete font, including smart font behaviour. See 2.8 and http://scripts.sil.org/OFL_web_fonts_and_RFNs + +2.7 Are there any situations in which a modified web font could use RFNs? +Yes. If a web font is optimized only in ways that preserve Functional Equivalence (see 2.8), then it may use RFNs, as it reasonably represents the Original Version and respects the intentions of the author(s) and the main purposes of the RFN mechanism (avoids collisions, protects authors, minimizes support, encourages derivatives). However this is technically very difficult and often impractical, so a much better scenario is for the web font service or provider to sign a separate agreement with the author(s) that allows the use of RFNs for Modified Versions. + +2.8 How do you know if an optimization to a web font preserves Functional Equivalence? +Functional Equivalence is described in full in the 'Web fonts and RFNs' paper at http://scripts.sil.org/OFL_web_fonts_and_RFNs, in general, an optimized font is deemed to be Functionally Equivalent (FE) to the Original Version if it: + +- Supports the same full character inventory. If a character can be properly displayed using the Original Version, then that same character, encoded correctly on a web page, will display properly. +- Provides the same smart font behavior. Any dynamic shaping behavior that works with the Original Version should work when optimized, unless the browser or environment does not support it. There does not need to be guaranteed support in the client, but there should be no forced degradation of smart font or shaping behavior, such as the removal or obfuscation of OpenType, Graphite or AAT tables. +- Presents text with no obvious degradation in visual quality. The lettershapes should be equally (or more) readable, within limits of the rendering platform. +- Preserves original author, project and license metadata. At a minimum, this should include: Copyright and authorship; The license as stated in the Original Version, whether that is the full text of the OFL or a link to the web version; Any RFN declarations; Information already present in the font or documentation that points back to the Original Version, such as a link to the project or the author's website. + +If an optimized font meets these requirements, and so is considered to be FE, then it's very likely that the original author would feel that the optimized font is a good and reasonable equivalent. If it falls short of any of these requirements, the optimized font does not reasonably represent the Original Version, and so should be considered to be a Modified Version. Like other Modified Versions, it would not be allowed to use any RFNs and you simply need to pick your own font name. + +2.9 Isn't use of web fonts another form of embedding? +No. Unlike embedded fonts in a PDF, web fonts are not an integrated part of the document itself. They are not specific to a single document and are often applied to thousands of documents around the world. The font data is not stored alongside the document data and often originates from a different location. The ease by which the web fonts used by a document may be identified and downloaded for desktop use demonstrates that they are philosophically and technically separate from the web pages that specify them. See http://scripts.sil.org/OFL_web_fonts_and_RFNs + +2.10 So would it be better to not use RFNs at all if you want your font to be distributed by a web fonts service? +No. Although the OFL does not require authors to use RFNs, the RFN mechanism is an important part of the OFL model and completely compatible with web font services. If that web font service modifies the fonts, then the best solution is to sign a separate agreement for the use of any RFNs. It is perfectly valid for an author to not declare any RFNs, but before they do so they need to fully understand the benefits they are giving up, and the overall negative effect of allowing many different versions bearing the same name to be widely distributed. As a result, we don't generally recommend it. + +2.11 What should an agreement for the use of RFNs say? Are there any examples? +There is no prescribed format for this agreement, as legal systems vary, and no recommended examples. Authors may wish to add specific clauses to further restrict use, require author review of Modified Versions, establish user support mechanisms or provide terms for ending the agreement. Such agreements are usually not public, and apply only to the main parties. However, it would be very beneficial for web font services to clearly state when they have established such agreements, so that the public understands clearly that their service is operating appropriately. + +See the separate paper on 'Web Fonts & RFNs' for in-depth discussion of issues related to the use of RFNs for web fonts. This is available at http://scripts.sil.org/OFL_web_fonts_and_RFNs + + +3 MODIFYING OFL-LICENSED FONTS + +3.1 Can I change the fonts? Are there any limitations to what things I can and cannot change? +You are allowed to change anything, as long as such changes do not violate the terms of the license. In other words, you are not allowed to remove the copyright statement(s) from the font, but you could put additional information into it that covers your contribution. See the placeholders in the OFL header template for recommendations on where to add your own statements. (Remember that, when authors have reserved names via the RFN mechanism, you need to change the internal names of the font to your own font name when making your modified version even if it is just a small change.) + +3.2 I have a font that needs a few extra glyphs - can I take them from an OFL licensed font and copy them into mine? +Yes, but if you distribute that font to others it must be under the OFL, and include the information mentioned in condition 2 of the license. + +3.3 Can I charge people for my additional work? In other words, if I add a bunch of special glyphs or OpenType/Graphite/AAT code, can I sell the enhanced font? +Not by itself. Derivative fonts must be released under the OFL and cannot be sold by themselves. It is permitted, however, to include them in a larger software package (such as text editors, office suites or operating systems), even if the larger package is sold. In that case, you are strongly encouraged, but not required, to also make that derived font easily and freely available outside of the larger package. + +3.4 Can I pay someone to enhance the fonts for my use and distribution? +Yes. This is a good way to fund the further development of the fonts. Keep in mind, however, that if the font is distributed to others it must be under the OFL. You won't be able to recover your investment by exclusively selling the font, but you will be making a valuable contribution to the community. Please remember how you have benefited from the contributions of others. + +3.5 I need to make substantial revisions to the font to make it work with my program. It will be a lot of work, and a big investment, and I want to be sure that it can only be distributed with my program. Can I restrict its use? +No. If you redistribute a Modified Version of the font it must be under the OFL. You may not restrict it in any way beyond what the OFL permits and requires. This is intended to ensure that all released improvements to the fonts become available to everyone. But you will likely get an edge over competitors by being the first to distribute a bundle with the enhancements. Again, please remember how you have benefited from the contributions of others. + +3.6 Do I have to make any derivative fonts (including extended source files, build scripts, documentation, etc.) publicly available? +No, but please consider sharing your improvements with others. You may find that you receive in return more than what you gave. + +3.7 If a trademark is claimed in the OFL font, does that trademark need to remain in modified fonts? +Yes. Any trademark notices must remain in any derivative fonts to respect trademark laws, but you may add any additional trademarks you claim, officially registered or not. For example if an OFL font called "Foo" contains a notice that "Foo is a trademark of Acme", then if you rename the font to "Bar" when creating a Modified Version, the new trademark notice could say "Foo is a trademark of Acme Inc. - Bar is a trademark of Roadrunner Technologies Ltd.". Trademarks work alongside the OFL and are not subject to the terms of the licensing agreement. The OFL does not grant any rights under trademark law. Bear in mind that trademark law varies from country to country and that there are no international trademark conventions as there are for copyright. You may need to significantly invest in registering and defending a trademark for it to remain valid in the countries you are interested in. This may be costly for an individual independent designer. + +3.8 If I commit changes to a font (or publish a branch in a DVCS) as part of a public open source software project, do I have to change the internal font names? +Only if there are declared RFNs. Making a public commit or publishing a public branch is effectively redistributing your modifications, so any change to the font will require that you do not use the RFNs. Even if there are no RFNs, it may be useful to change the name or add a suffix indicating that a particular version of the font is still in development and not released yet. This will clearly indicate to users and fellow designers that this particular font is not ready for release yet. See section 5 for more details. + + +4 LICENSING YOUR ORIGINAL FONTS UNDER THE OFL + +4.1 Can I use the SIL OFL for my own fonts? +Yes! We heartily encourage everyone to use the OFL to distribute their own original fonts. It is a carefully constructed license that allows great freedom along with enough artistic integrity protection for the work of the authors as well as clear rules for other contributors and those who redistribute the fonts. The licensing model is used successfully by various organisations, both for-profit and not-for-profit, to release fonts of varying levels of scope and complexity. + +4.2 What do I have to do to apply the OFL to my font? +If you want to release your fonts under the OFL, we recommend you do the following: + +4.2.1 Put your copyright and Reserved Font Names information at the beginning of the main OFL.txt file in place of the dedicated placeholders (marked with the <> characters). Include this file in your release package. + +4.2.2 Put your copyright and the OFL text with your chosen Reserved Font Name(s) into your font files (the copyright and license fields). A link to the OFL text on the OFL web site is an acceptable (but not recommended) alternative. Also add this information to any other components (build scripts, glyph databases, documentation, test files, etc). Accurate metadata in your font files is beneficial to you as an increasing number of applications are exposing this information to the user. For example, clickable links can bring users back to your website and let them know about other work you have done or services you provide. Depending on the format of your fonts and sources, you can use template human-readable headers or machine-readable metadata. You should also double-check that there is no conflicting metadata in the font itself contradicting the license, such as the fstype bits in the os2 table or fields in the name table. + +4.2.3 Write an initial FONTLOG.txt for your font and include it in the release package (see Section 6 and Appendix A for details including a template). + +4.2.4 Include the relevant practical documentation on the license by adding the current OFL-FAQ.txt file in your package. + +4.2.5 If you wish you can use the OFL graphics (http://scripts.sil.org/OFL_logo) on your website. + +4.3 Will you make my font OFL for me? +We won't do the work for you. We can, however, try to answer your questions, unfortunately we do not have the resources to review and check your font packages for correct use of the OFL. We recommend you turn to designers, foundries or consulting companies with experience in doing open font design to provide this service to you. + +4.4 Will you distribute my OFL font for me? +No, although if the font is of sufficient quality and general interest we may include a link to it on our partial list of OFL fonts on the OFL web site. You may wish to consider other open font catalogs or hosting services, such as the Unifont Font Guide (http://unifont.org/fontguide), The League of Movable Type (http://theleagueofmovabletype.com) or the Open Font Library (http://openfontlibrary.org/), which despite the name has no direct relationship to the OFL or SIL. We do not endorse any particular catalog or hosting service - it is your responsibility to determine if the service is right for you and if it treats authors with fairness. + +4.5 Why should I use the OFL for my fonts? +- to meet needs for fonts that can be modified to support lesser-known languages +- to provide a legal and clear way for people to respect your work but still use it (and reduce piracy) +- to involve others in your font project +- to enable your fonts to be expanded with new weights and improved writing system/language support +- to allow more technical font developers to add features to your design (such as OpenType, Graphite or AAT support) +- to renew the life of an old font lying on your hard drive with no business model +- to allow your font to be included in Libre Software operating systems like Ubuntu +- to give your font world status and wide, unrestricted distribution +- to educate students about quality typeface and font design +- to expand your test base and get more useful feedback +- to extend your reach to new markets when users see your metadata and go to your website +- to get your font more easily into one of the web font online services +- to attract attention for your commercial fonts +- to make money through web font services +- to make money by bundling fonts with applications +- to make money adjusting and extending existing open fonts +- to get a better chance that foundations/NGOs/charities/companies who commission fonts will pick you +- to be part of a sharing design and development community +- to give back and contribute to a growing body of font sources + + +5 CHOOSING RESERVED FONT NAMES + +5.1 What are Reserved Font Names? +These are font names, or portions of font names, that the author has chosen to reserve for use only with the Original Version of the font, or for Modified Version(s) created by the original author. + +5.2 Why can't I use the Reserved Font Names in my derivative font names? I'd like people to know where the design came from. +The best way to acknowledge the source of the design is to thank the original authors and any other contributors in the files that are distributed with your revised font (although no acknowledgement is required). The FONTLOG is a natural place to do this. Reserved Font Names ensure that the only fonts that have the original names are the unmodified Original Versions. This allows designers to maintain artistic integrity while allowing collaboration to happen. It eliminates potential confusion and name conflicts. When choosing a name, be creative and avoid names that reuse almost all the same letters in the same order or sound like the original. It will help everyone if Original Versions and Modified Versions can easily be distinguished from one another and from other derivatives. Any substitution and matching mechanism is outside the scope of the license. + +5.3 What do you mean by "primary name as presented to the user"? Are you referring to the font menu name? +Yes, this applies to the font menu name and other mechanisms that specify a font in a document. It would be fine, however, to keep a text reference to the original fonts in the description field, in your modified source file or in documentation provided alongside your derivative as long as no one could be confused that your modified source is the original. But you cannot use the Reserved Font Names in any way to identify the font to the user (unless the Copyright Holder(s) allow(s) it through a separate agreement). Users who install derivatives (Modified Versions) on their systems should not see any of the original Reserved Font Names in their font menus, for example. Again, this is to ensure that users are not confused and do not mistake one font for another and so expect features only another derivative or the Original Version can actually offer. + +5.4 Am I not allowed to use any part of the Reserved Font Names? +You may not use individual words from the Reserved Font Names, but you would be allowed to use parts of words, as long as you do not use any word from the Reserved Font Names entirely. We do not recommend using parts of words because of potential confusion, but it is allowed. For example, if "Foobar" was a Reserved Font Name, you would be allowed to use "Foo" or "bar", although we would not recommend it. Such an unfortunate choice would confuse the users of your fonts as well as make it harder for other designers to contribute. + +5.5 So what should I, as an author, identify as Reserved Font Names? +Original authors are encouraged to name their fonts using clear, distinct names, and only declare the unique parts of the name as Reserved Font Names. For example, the author of a font called "Foobar Sans" would declare "Foobar" as a Reserved Font Name, but not "Sans", as that is a common typographical term, and may be a useful word to use in a derivative font name. Reserved Font Names should also be single words for simplicity and legibility. A font called "Flowing River" should have Reserved Font Names "Flowing" and "River", not "Flowing River". You also need to be very careful about reserving font names which are already linked to trademarks (whether registered or not) which you do not own. + +5.6 Do I, as an author, have to identify any Reserved Font Names? +No. RFNs are optional and not required, but we encourage you to use them. This is primarily to avoid confusion between your work and Modified Versions. As an author you can release a font under the OFL and not declare any Reserved Font Names. There may be situations where you find that using no RFNs and letting your font be changed and modified - including any kind of modification - without having to change the original name is desirable. However you need to be fully aware of the consequences. There will be no direct way for end-users and other designers to distinguish your Original Version from many Modified Versions that may be created. You have to trust whoever is making the changes and the optimizations to not introduce problematic changes. The RFNs you choose for your own creation have value to you as an author because they allow you to maintain artistic integrity and keep some control over the distribution channel to your end-users. For discussion of RFNs and web fonts see section 2. + +5.7 Are any names (such as the main font name) reserved by default? +No. That is a change to the license as of version 1.1. If you want any names to be Reserved Font Names, they must be specified after the copyright statement(s). + +5.8 Is there any situation in which I can use Reserved Font Names for a Modified Version? +The Copyright Holder(s) can give certain trusted parties the right to use any of the Reserved Font Names through separate written agreements. For example, even if "Foobar" is a RFN, you could write up an agreement to give company "XYZ" the right to distribute a modified version with a name that includes "Foobar". This allows for freedom without confusion. The existence of such an agreement should be made as clear as possible to downstream users and designers in the distribution package and the relevant documentation. They need to know if they are a party to the agreement or not and what they are practically allowed to do or not even if all the details of the agreement are not public. + +5.9 Do font rebuilds require a name change? Do I have to change the name of the font when my packaging workflow includes a full rebuild from source? +Yes, all rebuilds which change the font data and the smart code are Modified Versions and the requirements of the OFL apply: you need to respect what the Author(s) have chosen in terms of Reserved Font Names. However if a package (or installer) is simply a wrapper or a compressed structure around the final font - leaving them intact on the inside - then no name change is required. Please get in touch with the author(s) and copyright holder(s) to inquire about the presence of font sources beyond the final font file(s) and the recommended build path. That build path may very well be non-trivial and hard to reproduce accurately by the maintainer. If a full font build path is made available by the upstream author(s) please be aware that any regressions and changes you may introduce when doing a rebuild for packaging purposes is your own responsibility as a package maintainer since you are effectively creating a separate branch. You should make it very clear to your users that your rebuilt version is not the canonical one from upstream. + +5.10 Can I add other Reserved Font Names when making a derivative font? +Yes. List your additional Reserved Font Names after your additional copyright statement, as indicated with example placeholders at the top of the OFL.txt file. Be sure you do not remove any existing RFNs but only add your own. RFN statements should be placed next to the copyright statement of the relevant author as indicated in the OFL.txt template to make them visible to designers wishing to make their separate version. + + +6 ABOUT THE FONTLOG + +6.1 What is this FONTLOG thing exactly? +It has three purposes: 1) to provide basic information on the font to users and other designers and developers, 2) to document changes that have been made to the font or accompanying files, either by the original authors or others, and 3) to provide a place to acknowledge authors and other contributors. Please use it! + +6.2 Is the FONTLOG required? +It is not a requirement of the license, but we strongly recommend you have one. + +6.3 Am I required to update the FONTLOG when making Modified Versions? +No, but users, designers and other developers might get very frustrated with you if you don't. People need to know how derivative fonts differ from the original, and how to take advantage of the changes, or build on them. There are utilities that can help create and maintain a FONTLOG, such as the FONTLOG support in FontForge. + +6.4 What should the FONTLOG look like? +It is typically a separate text file (FONTLOG.txt), but can take other formats. It commonly includes these four sections: + +- brief header describing the FONTLOG itself and name of the font family +- Basic Font Information - description of the font family, purpose and breadth +- ChangeLog - chronological listing of changes +- Acknowledgements - list of authors and contributors with contact information + +It could also include other sections, such as: where to find documentation, how to make contributions, information on contributing organizations, source code details, and a short design guide. See Appendix A for an example FONTLOG. + + +7 MAKING CONTRIBUTIONS TO OFL PROJECTS + +7.1 Can I contribute work to OFL projects? +In many cases, yes. It is common for OFL fonts to be developed by a team of people who welcome contributions from the wider community. Contact the original authors for specific information on how to participate in their projects. + +7.2 Why should I contribute my changes back to the original authors? +It would benefit many people if you contributed back in response to what you've received. Your contributions and improvements to the fonts and other components could be a tremendous help and would encourage others to contribute as well and 'give back'. You will then benefit from other people's contributions as well. Sometimes maintaining your own separate version takes more effort than merging back with the original. Be aware that any contributions, however, must be either your own original creation or work that you own, and you may be asked to affirm that clearly when you contribute. + +7.3 I've made some very nice improvements to the font. Will you consider adopting them and putting them into future Original Versions? +Most authors would be very happy to receive such contributions. Keep in mind that it is unlikely that they would want to incorporate major changes that would require additional work on their end. Any contributions would likely need to be made for all the fonts in a family and match the overall design and style. Authors are encouraged to include a guide to the design with the fonts. It would also help to have contributions submitted as patches or clearly marked changes - the use of smart source revision control systems like subversion, mercurial, git or bzr is a good idea. Please follow the recommendations given by the author(s) in terms of preferred source formats and configuration parameters for sending contributions. If this is not indicated in a FONTLOG or other documentation of the font, consider asking them directly. Examples of useful contributions are bug fixes, additional glyphs, stylistic alternates (and the smart font code to access them) or improved hinting. Keep in mind that some kinds of changes (esp. hinting) may be technically difficult to integrate. + +7.4 How can I financially support the development of OFL fonts? +It is likely that most authors of OFL fonts would accept financial contributions - contact them for instructions on how to do this. Such contributions would support future development. You can also pay for others to enhance the fonts and contribute the results back to the original authors for inclusion in the Original Version. + + +8 ABOUT THE LICENSE ITSELF + +8.1 I see that this is version 1.1 of the license. Will there be later changes? +Version 1.1 is the first minor revision of the OFL. We are confident that version 1.1 will meet most needs, but are open to future improvements. Any revisions would be for future font releases, and previously existing licenses would remain in effect. No retroactive changes are possible, although the Copyright Holder(s) can re-release the font under a revised OFL. All versions will be available on our web site: http://scripts.sil.org/OFL. + +8.2 Does this license restrict the rights of the Copyright Holder(s)? +No. The Copyright Holder(s) still retain(s) all the rights to their creation; they are only releasing a portion of it for use in a specific way. For example, the Copyright Holder(s) may choose to release a 'basic' version of their font under the OFL, but sell a restricted 'enhanced' version. Only the Copyright Holder(s) can do this. + +8.3 Is the OFL a contract or a license? +The OFL is a license and not a contract and so does not require you to sign it to have legal validity. By using, modifying and redistributing components under the OFL you indicate that you accept the license. + +8.4 I really like the terms of the OFL, but want to change it a little. Am I allowed to take ideas and actual wording from the OFL and put them into my own custom license for distributing my fonts? +We strongly recommend against creating your very own unique open licensing model. Using a modified or derivative license will likely cut you off - along with the font(s) under that license - from the community of designers using the OFL, potentially expose you and your users to legal liabilities, and possibly put your work and rights at risk. The OFL went though a community and legal review process that took years of effort, and that review is only applicable to an unmodified OFL. The text of the OFL has been written by SIL (with review and consultation from the community) and is copyright (c) 2005-2013 SIL International. You may re-use the ideas and wording (in part, not in whole) in another non-proprietary license provided that you call your license by another unambiguous name, that you do not use the preamble, that you do not mention SIL and that you clearly present your license as different from the OFL so as not to cause confusion by being too similar to the original. If you feel the OFL does not meet your needs for an open license, please contact us. + +8.5 Can I translate the license and the FAQ into other languages? +SIL certainly recognises the need for people who are not familiar with English to be able to understand the OFL and its use. Making the license very clear and readable has been a key goal for the OFL, but we know that people understand their own language best. + +If you are an experienced translator, you are very welcome to translate the OFL and OFL-FAQ so that designers and users in your language community can understand the license better. But only the original English version of the license has legal value and has been approved by the community. Translations do not count as legal substitutes and should only serve as a way to explain the original license. SIL - as the author and steward of the license for the community at large - does not approve any translation of the OFL as legally valid because even small translation ambiguities could be abused and create problems. + +SIL gives permission to publish unofficial translations into other languages provided that they comply with the following guidelines: + +- Put the following disclaimer in both English and the target language stating clearly that the translation is unofficial: + +"This is an unofficial translation of the SIL Open Font License into . It was not published by SIL International, and does not legally state the distribution terms for fonts that use the OFL. A release under the OFL is only valid when using the original English text. However, we recognize that this unofficial translation will help users and designers not familiar with English to better understand and use the OFL. We encourage designers who consider releasing their creation under the OFL to read the OFL-FAQ in their own language if it is available. Please go to http://scripts.sil.org/OFL for the official version of the license and the accompanying OFL-FAQ." + +- Keep your unofficial translation current and update it at our request if needed, for example if there is any ambiguity which could lead to confusion. + +If you start such a unofficial translation effort of the OFL and OFL-FAQ please let us know. + + +9 ABOUT SIL INTERNATIONAL + +9.1 Who is SIL International and what do they do? +SIL serves language communities worldwide, building their capacity for sustainable language development, by means of research, translation, training and materials development. SIL makes its services available to all without regard to religious belief, political ideology, gender, race, or ethnic background. SIL's members and volunteers share a Christian commitment. + +9.2 What does this have to do with font licensing? +The ability to read, write, type and publish in one's own language is one of the most critical needs for millions of people around the world. This requires fonts that are widely available and support lesser-known languages. SIL develops - and encourages others to develop - a complete stack of writing systems implementation components available under open licenses. This open stack includes input methods, smart fonts, smart rendering libraries and smart applications. There has been a need for a common open license that is specifically applicable to fonts and related software (a crucial component of this stack), so SIL developed the SIL Open Font License with the help of the Free/Libre and Open Source Software community. + +9.3 How can I contact SIL? +Our main web site is: http://www.sil.org/ +Our site about complex scripts is: http://scripts.sil.org/ +Information about this license (and contact information) is at: http://scripts.sil.org/OFL + + +APPENDIX A - FONTLOG EXAMPLE + +Here is an example of the recommended format for a FONTLOG, although other formats are allowed. + +----- +FONTLOG for the GlobalFontFamily fonts + +This file provides detailed information on the GlobalFontFamily Font Software. This information should be distributed along with the GlobalFontFamily fonts and any derivative works. + +Basic Font Information + +GlobalFontFamily is a Unicode typeface family that supports all languages that use the Latin script and its variants, and could be expanded to support other scripts. + +NewWorldFontFamily is based on the GlobalFontFamily and also supports Greek, Hebrew, Cyrillic and Armenian. + +More specifically, this release supports the following Unicode ranges... +This release contains... +Documentation can be found at... +To contribute to the project... + +ChangeLog + +10 December 2010 (Fred Foobar) GlobalFontFamily-devel version 1.4 +- fix new build and testing system (bug #123456) + +1 August 2008 (Tom Parker) GlobalFontFamily version 1.2.1 +- Tweaked the smart font code (Branch merged with trunk version) +- Provided improved build and debugging environment for smart behaviours + +7 February 2007 (Pat Johnson) NewWorldFontFamily Version 1.3 +- Added Greek and Cyrillic glyphs + +7 March 2006 (Fred Foobar) NewWorldFontFamily Version 1.2 +- Tweaked contextual behaviours + +1 Feb 2005 (Jane Doe) NewWorldFontFamily Version 1.1 +- Improved build script performance and verbosity +- Extended the smart code documentation +- Corrected minor typos in the documentation +- Fixed position of combining inverted breve below (U+032F) +- Added OpenType/Graphite smart code for Armenian +- Added Armenian glyphs (U+0531 -> U+0587) +- Released as "NewWorldFontFamily" + +1 Jan 2005 (Joe Smith) GlobalFontFamily Version 1.0 +- Initial release + +Acknowledgements + +If you make modifications be sure to add your name (N), email (E), web-address (if you have one) (W) and description (D). This list is in alphabetical order. + +N: Jane Doe +E: jane@university.edu +W: http://art.university.edu/projects/fonts +D: Contributor - Armenian glyphs and code + +N: Fred Foobar +E: fred@foobar.org +W: http://foobar.org +D: Contributor - misc Graphite fixes + +N: Pat Johnson +E: pat@fontstudio.org +W: http://pat.fontstudio.org +D: Designer - Greek & Cyrillic glyphs based on Roman design + +N: Tom Parker +E: tom@company.com +W: http://www.company.com/tom/projects/fonts +D: Engineer - original smart font code + +N: Joe Smith +E: joe@fontstudio.org +W: http://joe.fontstudio.org +D: Designer - original Roman glyphs + +Fontstudio.org is an not-for-profit design group whose purpose is... +Foobar.org is a distributed community of developers... +Company.com is a small business who likes to support community designers... +University.edu is a renowned educational institution with a strong design department... +----- + + diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/OFL.txt b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/OFL.txt new file mode 100644 index 00000000000..aef2579114f --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/shared/fonts/khmer/mondulkiri/OFL.txt @@ -0,0 +1,95 @@ +This Font Software is Copyright (c) 2003-2014, SIL International +(http://scripts.sil.org/). with Reserved Font Names "Mondulkiri", +"Busra", "Oureang" and "Ratanakiri". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +http://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. 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 new file mode 100644 index 00000000000..2272334352b --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/khmer_angkor.kps @@ -0,0 +1,131 @@ + + + + 15.0.266.0 + 7.0 + + + + readme.htm + splash.gif + + + + + + + + + + Khmer Angkor + © 2015-2022 SIL International + Makara Sok + + https://keyman.com/keyboards/khmer_angkor + + + + ..\build\khmer_angkor.js + File khmer_angkor.js + 0 + .js + + + ..\build\khmer_angkor.kvk + File khmer_angkor.kvk + 0 + .kvk + + + ..\build\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/khmer_angkor/source/readme.htm b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/readme.htm new file mode 100644 index 00000000000..36a95d92482 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/readme.htm @@ -0,0 +1,23 @@ + + + + + Khmer Angkor Keyboard + + + + +

Khmer Angkor Keyboard

+ +

+ With the users' pitfall in typing Khmer words in mind, Khmer Angkor focuses on the unification of the character orders and spelling rules. These make Khmer Angkor uniquely different from any other keyboards. Khmer Angkor's keyboard layouts were adopted from NiDA keyboard which is widely used at the present. +

+ +

© 2015-2022 SIL International

+ + + diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/source/splash.gif b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/splash.gif new file mode 100644 index 00000000000..93b44e9d16e Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/splash.gif differ diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/KAK_Documentation_EN.pdf b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/KAK_Documentation_EN.pdf new file mode 100644 index 00000000000..23b6f62b8fe Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/KAK_Documentation_EN.pdf differ diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/KAK_Documentation_KH.pdf b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/KAK_Documentation_KH.pdf new file mode 100644 index 00000000000..85bd8c0b0b7 Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/KAK_Documentation_KH.pdf differ diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/image002.png b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/image002.png new file mode 100644 index 00000000000..40ea6a2e8e4 Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/image002.png differ diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/keyboard_layout.png b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/keyboard_layout.png new file mode 100644 index 00000000000..8ca8b04b337 Binary files /dev/null and b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/keyboard_layout.png differ diff --git a/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/welcome.htm b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/welcome.htm new file mode 100644 index 00000000000..124d9d20e66 --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/khmer_angkor/source/welcome/welcome.htm @@ -0,0 +1,54 @@ + + + + Khmer Angkor Keyboard + + +

Khmer Angkor Keyboard

+ +

+ Khmer Angkor Keyboard is an OpenSource. It is free and more than just a user you can also contribute to the betterment of the keyboard if you would like to. +

+ +

Keyboards

+

+ With the users' pitfall in typing Khmer words in mind, Khmer Angkor focuses on the unification of the character orders and spelling rules. These make Khmer Angkor uniquely different from any other keyboards. Khmer Angkor's keyboard layouts were adopted from NiDA keyboard which is widely used at the present. + Here is the keyboard layout: +

+ + + + +

You can simply use Khmer Angkor as you would for any NiDA based keyboard. You may type a word in an order of how it is spelled, not how it appears to be, especially when the vowel is to the left of the consonant. For example, to type the word “តែ” which means ‘tea’, one should type the consonant first and then the vowel (i.e. press key “T” and then “Shift E”).

+ +

The order of characters is:

+

Consonant + Subscript(s) + Consonant Shifter + Vowel + Diacritic

+ +

For more details on the Khmer Angkor Keyboard, please find the documentation included in this package.

+ +

Keyboard Documentations

+

For the documentation in English, click here.

+

For the documentation in Khmer, click here.

+ +

Fonts

+

+ To get Khmer fonts, you may visit one of the three sources below: +

+ +

More Information

+

+ +

+ + + + + \ No newline at end of file diff --git a/developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/build/withfolders.qaa.sencoten.model.js b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/build/withfolders.qaa.sencoten.model.js similarity index 100% rename from developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/build/withfolders.qaa.sencoten.model.js rename to developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/build/withfolders.qaa.sencoten.model.js diff --git a/developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/source/welcome.htm b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/welcome.htm similarity index 100% rename from developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/source/welcome.htm rename to developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/welcome.htm diff --git a/developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.kps b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.kps similarity index 100% rename from developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.kps rename to developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.kps diff --git a/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.ts b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.ts new file mode 100644 index 00000000000..2a5715fa94a --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/withfolders.qaa.sencoten.model.ts @@ -0,0 +1,6 @@ +const source: LexicalModelSource = { + format: 'trie-1.0', + sources: ['wordlist.tsv'], + wordBreaker: 'default', +}; +export default source; diff --git a/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/wordlist.tsv b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/wordlist.tsv new file mode 100644 index 00000000000..a2a1ad5964d --- /dev/null +++ b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/source/wordlist.tsv @@ -0,0 +1,10 @@ +TŦE 13644 +E 9134 +SEN 4816 +Ȼ 3479 +SW̱ 2621 +NIȽ 2314 +U¸ 2298 +I¸ 1988 +ȻSE 1925 +I 1884 \ No newline at end of file diff --git a/developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.intermediate.json b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.intermediate.json similarity index 88% rename from developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.intermediate.json rename to developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.intermediate.json index e8d4ed34f49..046a2f3f39a 100644 --- a/developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.intermediate.json +++ b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.intermediate.json @@ -16,19 +16,21 @@ "description": "SENĆOŦEN (Saanich Dialect) Lexical Model" }, "version": { - "description": "1.0.3" + "description": "0.0.0" } }, "files": [ { "name": "..\\build\\withfolders.qaa.sencoten.model.js", - "description": "Lexical model withfolders.qaa.sencoten.model.js", - "copyLocation": 0 + "description": "Lexical model withfolders.qaa.sencoten.model.js" }, { - "copyLocation": 0, "description": "Welcome file", "name": "welcome.htm" + }, + { + "description": "Package information (JSON)", + "name": "kmp.json" } ], "lexicalModels": [ @@ -47,4 +49,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.zipped.json b/developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.zipped.json similarity index 100% rename from developer/src/kmlmc/tests/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.zipped.json rename to developer/src/kmc-package/test/fixtures/withfolders.qaa.sencoten/withfolders.qaa.sencoten.model.kmp.zipped.json diff --git a/developer/src/kmc-package/test/helpers/index.ts b/developer/src/kmc-package/test/helpers/index.ts new file mode 100644 index 00000000000..8250dfd101a --- /dev/null +++ b/developer/src/kmc-package/test/helpers/index.ts @@ -0,0 +1,17 @@ +/** + * 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/kmlmc/tests/test-package-compiler.ts b/developer/src/kmc-package/test/test-package-compiler.ts similarity index 50% rename from developer/src/kmlmc/tests/test-package-compiler.ts rename to developer/src/kmc-package/test/test-package-compiler.ts index cc6752595a9..5daa940de61 100644 --- a/developer/src/kmlmc/tests/test-package-compiler.ts +++ b/developer/src/kmc-package/test/test-package-compiler.ts @@ -1,17 +1,16 @@ import 'mocha'; import * as fs from 'fs'; -import * as JSZip from 'jszip'; import {assert} from 'chai'; -import KmpCompiler from '../dist/package-compiler/kmp-compiler'; -import {makePathToFixture} from './helpers'; - -let zip = JSZip(); +import KmpCompiler from '../src/kmp-compiler.js'; +import {makePathToFixture} from './helpers/index.js'; +import JSZip from 'jszip'; +import KEYMAN_VERSION from "@keymanapp/keyman-version/keyman-version.mjs"; describe('KmpCompiler', function () { - const MODELS = [ + const MODELS : string[] = [ 'example.qaa.sencoten', - 'withfolders.qaa.sencoten' + 'withfolders.qaa.sencoten', ]; let kmpCompiler = new KmpCompiler(); @@ -26,6 +25,9 @@ describe('KmpCompiler', function () { const kmpJsonIntermediateFixture = JSON.parse(fs.readFileSync(kmpJsonIntermediatePath, 'utf-8')); const kmpJsonZippedFixture = JSON.parse(fs.readFileSync(kmpJsonZippedPath, 'utf-8')); + // We override the fixture version so that we can compare with the compiler output + kmpJsonIntermediateFixture.system.keymanDeveloperVersion = KEYMAN_VERSION.VERSION; + // // Test just the transform from kps to kmp.json // @@ -40,16 +42,20 @@ describe('KmpCompiler', function () { // Test that the kmp.json data is identical assert.deepEqual(kmpJson, kmpJsonIntermediateFixture); + // Note that in-memory kmp.json still contains paths in the files array. + // However, when building the .kmp, the final written kmp.json data is + // modified to strip paths. + // This was used when building initial test data //fs.writeFileSync(kmpJsonPath, JSON.stringify(kmpJson), 'utf-8'); }); - it(`should build a full .kmp for ${modelID}`, async function() { const source = fs.readFileSync(kpsPath, 'utf-8'); + const zip = JSZip(); // Build kmp.json in memory const kmpJson: KmpJsonFile = kmpCompiler.transformKpsToKmpObject(source, kpsPath); // Build file.kmp in memory - const promise = kmpCompiler.buildKmpFile(kmpJson); + const promise = kmpCompiler.buildKmpFile(kpsPath, kmpJson); promise.then(data => { // Check that file.kmp contains just 2 files - kmp.json and file.model.js, // and that they match exactly what we expect @@ -69,4 +75,45 @@ describe('KmpCompiler', function () { return promise; }); } + + it('should generates a valid .kmp (zip) file', async function() { + // const kmpPath = makePathToFixture('khmer_angkor', 'build', 'khmer_angkor.kmp'); + const kpsPath = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps'); + const kmpJsonRefPath = makePathToFixture('khmer_angkor', 'ref', 'kmp.json'); + + const kmpCompiler = new KmpCompiler(); + const source = fs.readFileSync(kpsPath, 'utf-8'); + const kmpJsonFixture: KmpJsonFile = JSON.parse(fs.readFileSync(kmpJsonRefPath, 'utf-8')); + + // We override the fixture version so that we can compare with the compiler output + kmpJsonFixture.system.keymanDeveloperVersion = KEYMAN_VERSION.VERSION; + + let kmpJson = null; + assert.doesNotThrow(() => { + kmpJson = kmpCompiler.transformKpsToKmpObject(source, kpsPath); + }); + + const kmpData = await kmpCompiler.buildKmpFile(kpsPath, kmpJson); + + const zip = JSZip(); + + let jszip = await zip.loadAsync(kmpData); + assert.isNotNull(jszip.file('kmp.json')); // kmp.json should be present + // kmp file should contain the following files + const expectedFiles = [ + 'FONTLOG.txt', 'image002.png', 'KAK_Documentation_EN.pdf', 'KAK_Documentation_KH.pdf', + 'keyboard_layout.png', 'khmer_angkor.js', 'khmer_angkor.kmx', 'khmer_angkor.kvk', + 'khmer_busra_kbd.ttf', 'Mondulkiri-R.ttf', 'OFL.txt', 'OFL-FAQ.txt', 'readme.htm', + 'splash.gif', 'welcome.htm', + 'kmp.json', // standard .kmp metadata file + ]; + + assert.sameMembers(Object.entries(jszip.files).map(([s, o]) => o.name).sort(), + expectedFiles.sort(), + 'khmer_angkor.kmp file should have exactly the expected files'); + + let kmpJsonData = JSON.parse(await jszip.file('kmp.json').async('string')); + assert.deepEqual(kmpJsonData, kmpJsonFixture); + }); + }); diff --git a/developer/src/kmc-package/test/tsconfig.json b/developer/src/kmc-package/test/tsconfig.json new file mode 100644 index 00000000000..d785ebc0195 --- /dev/null +++ b/developer/src/kmc-package/test/tsconfig.json @@ -0,0 +1,19 @@ +{ + "extends": "../../kmc/tsconfig.kmc-base.json", + + "compilerOptions": { + "rootDir": ".", + "rootDirs": ["./", "../src/"], + "outDir": "../build/test", + "baseUrl": ".", + "allowSyntheticDefaultImports": true, // for jszip + }, + "include": [ + "**/test-*.ts", + "helpers/*.ts" + ], + "references": [ + { "path": "../" }, + { "path": "../../../../common/web/keyman-version/tsconfig.esm.json" }, + ] +} \ No newline at end of file diff --git a/developer/src/kmc-package/tsconfig.json b/developer/src/kmc-package/tsconfig.json new file mode 100644 index 00000000000..b7044ed6ee5 --- /dev/null +++ b/developer/src/kmc-package/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../kmc/tsconfig.kmc-base.json", + + "compilerOptions": { + "outDir": "build/src/", + "rootDir": "src/", + "allowSyntheticDefaultImports": true, // for jszip + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { "path": "../../../common/web/keyman-version/tsconfig.esm.json" }, + ] +} diff --git a/developer/src/kmlmc/.npmignore b/developer/src/kmc/.npmignore similarity index 51% rename from developer/src/kmlmc/.npmignore rename to developer/src/kmc/.npmignore index 2ac029d741a..8a5101350b1 100644 --- a/developer/src/kmlmc/.npmignore +++ b/developer/src/kmc/.npmignore @@ -1,9 +1,14 @@ # Ignore files required only in development. -dist-tests/* -source/* -tests/* build.sh -bundle.sh +bundle.inc.sh Makefile +src/* +test/* +tools/* tsconfig.json +tsconfig.kmc-base.json tsconfig.tsbuildinfo +dist-tests/* +tests/* +# Ignore other unneeded build artifacts +.nyc_output/ diff --git a/developer/src/kmc/Makefile b/developer/src/kmc/Makefile new file mode 100644 index 00000000000..8e8dc7d5f31 --- /dev/null +++ b/developer/src/kmc/Makefile @@ -0,0 +1,39 @@ +# +# Keyman Developer - next generation compiler Makefile +# + +!include ..\Defines.mak + +# We don't depend on configure here because kmc-keyboard does that already, +# called from parent folder Makefile +build: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh build + +configure: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh configure + +clean: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh clean + +test: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh test + +# build.sh bundle must be run from shell as it requires a temp folder to be +# passed in. See inst/download.in.mak for instantiation. + +publish: .virtual + $(GIT_BASH_FOR_KEYMAN) build.sh publish + +signcode: + @rem nothing to do + +wrap-symbols: + @rem nothing to do + +test-manifest: + @rem nothing to do + +install: + @rem nothing to do + +!include ..\Target.mak diff --git a/developer/src/kmc/README.md b/developer/src/kmc/README.md new file mode 100644 index 00000000000..f0c816bbb54 --- /dev/null +++ b/developer/src/kmc/README.md @@ -0,0 +1,96 @@ +Keyman Developer - Next Generation Compiler +================ + +This package provides the following Keyman **command line tools**: + + - `kmc` — takes **LDML Keyboard .xml sources** and compiles them in to a + KMXPlus **.kmx** file. + - `kmlmc` — takes **lexical model sources** and compiles them in to a **.js** + 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]. + +Note: `kmc` will in the future replace `kmlmc`, `kmlmp`, and `kmlmi`. + +In order to build [lexical models][], these tools must be built and compiled. + +[lexical models]: https://github.com/keymanapp/lexical-models + + +Install +------- + +Install `kmc` globally: + + npm install -g @keymanapp/kmc + +kmc Usage +--------- + +To compile an LDML keyboard from its `.xml` source, use `kmc`: + + kmc build my-keyboard.xml --outFile my-keyboard.kmx + +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`: + + kmlmc my-lexical-model.model.ts --outFile my-lexical-model.js + +To see more command line options by using the `--help` option: + + kmlmc --help + kmlmp --help + kmlmi --help + +How to build from source +------------------------ + +Run `build.sh`: + + ./build.sh configure build + +or (less preferably -- build.sh is more efficient): + + nmake configure build + +Once you have `configure`d once, you should not normally need to do it again +unless dependencies change or you clean the build folder. `./build.sh` without +parameters will do the default action, which is `build`. + +TODO: Note that kmc currently depends on kmc-* to have been configured; while +the build of kmc will do the typescript component of the build, it will not be +able to do any other build steps, so you may wish to build each of the +components separately, one time. + +How to run the tests +-------------------- + + ./build.sh test + + +How to prepare bundling for installation +---------------------------------------- + + ./build.sh bundle --build-path + +The temp_path must be a path outside the repository to avoid npm getting +confused by the root package.json. This is called by inst/download.in.mak +normally when building the Keyman Developer installer. + +How to publish to NPM +--------------------- + + ./build.sh publish [--dry-run] + +Publishes the current release to NPM. This should only be run from CI. \ No newline at end of file diff --git a/developer/src/kmc/build.sh b/developer/src/kmc/build.sh new file mode 100755 index 00000000000..f85302f25e7 --- /dev/null +++ b/developer/src/kmc/build.sh @@ -0,0 +1,104 @@ +#!/usr/bin/env bash +# +# Compiles the developer tools, including the language model compilers. +# + +# 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 + +cd "$THIS_SCRIPT_PATH" + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +builder_describe "Build Keyman Keyboard Compiler kmc" \ + "@/common/web/keyman-version" \ + "@/common/web/types" \ + "@../kmc-keyboard" \ + "@../kmc-model" \ + "@../kmc-model-info" \ + "@../kmc-package" \ + "configure runs 'npm ci' on root folder" \ + "build (default) builds kmc to build/" \ + "clean cleans build/ folder" \ + "bundle creates a bundled version of kmc" \ + "test run automated tests for kmc" \ + "publish publish to npm" \ + "--build-path=BUILD_PATH build directory for bundle" \ + "--dry-run,-n don't actually publish, just dry run" +builder_describe_outputs \ + configure /node_modules \ + build build/src/kmc.js + +builder_parse "$@" + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action clean; then + rm -rf ./build/ ./tsconfig.tsbuildinfo + builder_finish_action success clean +else + # We need the schema file at runtime and bundled, so always copy it for all actions except `clean` + mkdir -p "$THIS_SCRIPT_PATH/build/src/util/" + cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json" "$THIS_SCRIPT_PATH/build/src/util/" + cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json" "$THIS_SCRIPT_PATH/build/src/util/" + cp "$KEYMAN_ROOT/common/schemas/kvks/kvks.schema.json" "$THIS_SCRIPT_PATH/build/src/util/" + cp "$KEYMAN_ROOT/common/schemas/kpj/kpj.schema.json" "$THIS_SCRIPT_PATH/build/src/util/" +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 + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action test; then + # npm test -- no tests as yet + builder_finish_action success test +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action bundle; then + if ! builder_has_option --build-path; then + builder_finish_action "Parameter --build-path is required" bundle + exit 64 + fi + + mkdir -p build/cjs-src + npm run bundle + cp build/cjs-src/* "$BUILD_PATH" + + builder_finish_action success bundle +fi + +#------------------------------------------------------------------------------------------------------------------- + +if builder_start_action publish; then + . "$KEYMAN_ROOT/resources/build/npm-publish.inc.sh" + npm_publish + + # For now, kmc will have responsibility for publishing keyman-version. In + # the future, we should probably have a top-level npm publish script that + # publishes all modules for a given release version + # From: #7595 + "$KEYMAN_ROOT/common/web/keyman-version/build.sh" publish $DRY_RUN + + builder_finish_action success publish +fi diff --git a/developer/src/kmc/package.json b/developer/src/kmc/package.json new file mode 100644 index 00000000000..5515622e040 --- /dev/null +++ b/developer/src/kmc/package.json @@ -0,0 +1,72 @@ +{ + "name": "@keymanapp/kmc", + "description": "Keyman Developer compiler command line tools", + "keywords": [ + "keyboard", + "keyman", + "ldml", + "unicode", + "lexical-model", + "predictive-text" + ], + "scripts": { + "build": "tsc -b", + "bundle": "npm run bundle-kmc && npm run bundle-kmlmc && npm run bundle-kmlmi && npm run bundle-kmlmp", + "bundle-kmc": "esbuild build/src/kmc.js --bundle --platform=node > build/cjs-src/kmc.cjs", + "bundle-kmlmc": "esbuild build/src/kmlmc.js --bundle --platform=node > build/cjs-src/kmlmc.cjs", + "bundle-kmlmi": "esbuild build/src/kmlmi.js --bundle --platform=node > build/cjs-src/kmlmi.cjs", + "bundle-kmlmp": "esbuild build/src/kmlmp.js --bundle --platform=node > build/cjs-src/kmlmp.cjs", + "test": "cd test && tsc -b && cd .. && c8 --reporter=lcov --reporter=text mocha", + "prepublishOnly": "npm run build" + }, + "type": "module", + "author": "Marc Durdin (https://github.com/mcdurdin)", + "contributors": [ + "Eddie Antonio Santos ", + "Joshua Horton" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/keymanapp/keyman/issues" + }, + "main": "build/src/kmc.js", + "bin": { + "kmc": "build/src/kmc.js", + "kmlmc": "build/src/kmlmc.js", + "kmlmp": "build/src/kmlmp.js", + "kmlmi": "build/src/kmlmi.js" + }, + "dependencies": { + "@keymanapp/keyman-version": "*", + "@keymanapp/common-types": "*", + "@keymanapp/kmc-keyboard": "*", + "@keymanapp/kmc-model": "*", + "@keymanapp/kmc-model-info": "*", + "@keymanapp/kmc-package": "*", + "@keymanapp/models-types": "*", + "commander": "^10.0.0" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "esbuild": "^0.15.8", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "^4.5.4" + }, + "mocha": { + "spec": "build/test/**/test-*.js", + "require": [ + "source-map-support/register" + ] + }, + "repository": { + "type": "git", + "url": "git+https://github.com/keymanapp/keyman.git" + } +} diff --git a/developer/src/kmc/src/activities/buildKmnKeyboard.ts b/developer/src/kmc/src/activities/buildKmnKeyboard.ts new file mode 100644 index 00000000000..421c59c6138 --- /dev/null +++ b/developer/src/kmc/src/activities/buildKmnKeyboard.ts @@ -0,0 +1,54 @@ +import { spawnSync } from 'child_process'; +import * as path from 'path'; +import { BuildCommandOptions } from '../commands/build.js'; +import { getDeveloperBinPath } from '../util/getDeveloperBinPath.js'; + +export async function buildKmnKeyboard(infile: string, options: BuildCommandOptions): Promise { + // We'll call out to kmcomp.exe to build a .kmn keyboard into a .kmx Note: + // kmcomp.exe will also build a .js if it is required, and may not actually + // generate a .kmx if the output target defined in the .kmn is mobile/web + // only. kmcomp.exe will also build the .kvk file. + const binRoot = getDeveloperBinPath(); + if(binRoot == null) { + console.error('Could not locate Keyman Developer bin path'); + return false; + } + + let args = ['-nologo']; + if(options.debug) { + args.push('-d'); + } + + args.push(path.win32.normalize(infile)); + + // .* target file name option allows us to specify .kmx output file and the + // .js output file will also be generated as required, in the same way as when + // we build a project. Note: this is a stop gap as we work to deprecate + // kmcomp.exe and replace it with kmcmp (cross-platform .kmx compiler in C++) + // + kmc-kmw (KMX->KMW transpiler in TypeScript) + kmc-kvk (KVK compiler in + // TypeScript). + let outfile = (options.outFile ?? infile).replace(/\\/g, '/'); + // For now, we normalize to posix, and then renormalize to win32 for launching + // kmcomp, until we have a cross-platform alternative to use + outfile = path.win32.normalize(options.outFile).replace(/\.km.$/i, '.*'); + args.push(outfile); + + const kmcomp = path.join(binRoot, 'kmcomp.exe'); + + // We won't attempt to make this call cross-platform, yet. + let child = spawnSync(kmcomp, args, { + encoding: 'utf8' + }); + + if(child.error) { + console.error(child.error); + return false; + } + + console.log(child.stdout); + if(child.stderr) { + console.error(child.stderr); + } + + return child.status === 0; +} \ No newline at end of file diff --git a/developer/src/kmc/src/activities/buildLdmlKeyboard.ts b/developer/src/kmc/src/activities/buildLdmlKeyboard.ts new file mode 100644 index 00000000000..9fa7814afdb --- /dev/null +++ b/developer/src/kmc/src/activities/buildLdmlKeyboard.ts @@ -0,0 +1,84 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import * as kmc from '@keymanapp/kmc-keyboard'; +import { KvkFileWriter, CompilerCallbacks } from '@keymanapp/common-types'; +import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js'; +import { BuildCommandOptions } from '../commands/build.js'; + +export async function buildLdmlKeyboard(infile: string, options: BuildCommandOptions): Promise { + // TODO-LDML: consider hardware vs touch -- touch-only layout will not have a .kvk + // Compile: + let [kmx,kvk,kmw] = buildLdmlKeyboardToMemory(infile, options); + // Output: + + const fileBaseName = options.outFile ?? infile; + const outFileBase = path.basename(fileBaseName, path.extname(fileBaseName)); + const outFileDir = path.dirname(fileBaseName); + + if(kmx && kvk) { + const outFileKmx = path.join(outFileDir, outFileBase + '.kmx'); + console.log(`Writing compiled keyboard to ${outFileKmx}`); + fs.writeFileSync(outFileKmx, kmx); + + const outFileKvk = path.join(outFileDir, outFileBase + '.kvk'); + console.log(`Writing compiled visual keyboard to ${outFileKvk}`); + fs.writeFileSync(outFileKvk, kvk); + } else { + console.error(`An error occurred compiling ${infile}`); + return false; + } + + if(kmw) { + const outFileKmw = path.join(outFileDir, outFileBase + '.js'); + console.log(`Writing compiled js keyboard to ${outFileKmw}`); + fs.writeFileSync(outFileKmw, kmw); + } + + return true; +} + +function buildLdmlKeyboardToMemory(inputFilename: string, options: BuildCommandOptions): [Uint8Array, Uint8Array, Uint8Array] { + let compilerOptions: kmc.CompilerOptions = { + debug: options.debug ?? false, + addCompilerVersion: options.compilerVersion ?? true + } + + const c: CompilerCallbacks = new NodeCompilerCallbacks(); + const k = new kmc.Compiler(c, options); + let source = k.load(inputFilename); + if (!source) { + return [null, null, null]; + } + if (!k.validate(source)) { + return [null, null, null]; + } + let kmx = k.compile(source); + if (!kmx) { + return [null, null, null]; + } + + // In order for the KMX file to be loaded by non-KMXPlus components, it is helpful + // to duplicate some of the metadata + kmc.KMXPlusMetadataCompiler.addKmxMetadata(kmx.kmxplus, kmx.keyboard, compilerOptions); + + // Use the builder to generate the binary output file + const builder = new kmc.KMXBuilder(kmx, options.debug); + const kmx_binary = builder.compile(); + + const vkcompiler = new kmc.VisualKeyboardCompiler(); + const vk = vkcompiler.compile(source); + const writer = new KvkFileWriter(); + const kvk_binary = writer.write(vk); + + // Note: we could have a step of generating source files here + // KvksFileWriter()... + // const tlcompiler = new kmc.TouchLayoutCompiler(); + // const tl = tlcompiler.compile(source); + // const tlwriter = new TouchLayoutFileWriter(); + const kmwcompiler = new kmc.KeymanWebCompiler(compilerOptions); + const kmw_string = kmwcompiler.compile(inputFilename, source); + const encoder = new TextEncoder(); + const kmw_binary = encoder.encode(kmw_string); + + return [kmx_binary, kvk_binary, kmw_binary]; +} diff --git a/developer/src/kmc/src/activities/buildModel.ts b/developer/src/kmc/src/activities/buildModel.ts new file mode 100644 index 00000000000..dc0ddf6e9bf --- /dev/null +++ b/developer/src/kmc/src/activities/buildModel.ts @@ -0,0 +1,28 @@ +import * as fs from 'fs'; +import { BuildCommandOptions } from '../commands/build.js'; +import { compileModel } from '@keymanapp/kmc-model'; + +export async function buildModel(infile: string, options: BuildCommandOptions): Promise { + + let outputFilename: string = options.outFile ? options.outFile : infile.replace(/\.ts$/i, ".js"); + + let code = null; + + // Compile: + try { + code = compileModel(infile); + } catch(e) { + console.error(e); + return false; + } + + if(!code) { + console.error('Compilation failed.') + return false; + } + + // Output: + fs.writeFileSync(outputFilename, code, 'utf8'); + + return true; +} \ No newline at end of file diff --git a/developer/src/kmc/src/activities/buildPackage.ts b/developer/src/kmc/src/activities/buildPackage.ts new file mode 100644 index 00000000000..7138c29edaf --- /dev/null +++ b/developer/src/kmc/src/activities/buildPackage.ts @@ -0,0 +1,30 @@ +import * as fs from 'fs'; +import { BuildCommandOptions } from '../commands/build.js'; +import KmpCompiler from '@keymanapp/kmc-package'; + +export async function buildPackage(infile: string, options: BuildCommandOptions): Promise { + + let outputFilename: string = options.outFile ? options.outFile : infile.replace(/\.kps$/i, ".kmp"); + + // + // Load .kps source data + // + + let kpsString: string = fs.readFileSync(infile, 'utf8'); + let kmpCompiler = new KmpCompiler(); + let kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString, infile); + + // + // Build the .kmp package file + // + + let data = await kmpCompiler.buildKmpFile(infile, kmpJsonData); + if(data) { + fs.writeFileSync(outputFilename, data, 'binary'); + } else { + // TODO error logging + return false; + } + + return true; +} \ No newline at end of file diff --git a/developer/src/kmc/src/activities/buildProject.ts b/developer/src/kmc/src/activities/buildProject.ts new file mode 100644 index 00000000000..78acdbef3c6 --- /dev/null +++ b/developer/src/kmc/src/activities/buildProject.ts @@ -0,0 +1,145 @@ +import { CompilerCallbacks, KeymanDeveloperProject, KPJFileReader } from '@keymanapp/common-types'; +import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js'; +import { KeymanDeveloperProjectFile } from '../../../../../common/web/types/src/kpj/keyman-developer-project.js'; +import { BuildCommandOptions } from '../commands/build.js'; +import { buildKmnKeyboard } from './buildKmnKeyboard.js'; +import * as path from 'path'; +import * as fs from 'fs'; +import { buildLdmlKeyboard } from './buildLdmlKeyboard.js'; +import { buildModel } from './buildModel.js'; +import { buildPackage } from './buildPackage.js'; + +export async function buildProject(infile: string, options: BuildCommandOptions): Promise { + let builder = new ProjectBuilder(infile, options); + return builder.run(); +} + +class ProjectBuilder { + callbacks: CompilerCallbacks = new NodeCompilerCallbacks(); + infile: string; + options: BuildCommandOptions; + project: KeymanDeveloperProject; + + constructor(infile: string, options: BuildCommandOptions) { + this.infile = path.resolve(infile); + this.options = options; + } + + async run(): Promise { + if(this.options.outFile) { + // TODO: callbacks.reportMessage + console.error('--out-file should not be specified for project builds'); + return false; + } + + this.project = this.loadProject(); + if(!this.project) { + return false; + } + + // Build all Keyman keyboards in the project + if(!await this.buildProjectTargets(buildKmnKeyboard, '.kmn', '.kmx')) { + return false; + } + + // Build all LDML keyboards in the project + if(!await this.buildProjectTargets(buildLdmlKeyboard, '.xml', '.kmx')) { + return false; + } + + // Build all models in the project + if(!await this.buildProjectTargets(buildModel, '.model.ts', '.model.js')) { + return false; + } + + // Build all packages in the project + if(!await this.buildProjectTargets(buildPackage, '.kps', '.kmp')) { + return false; + } + + // TODO: generate .keyboard_info from .kps + etc (and support merge of + // $PROJECTPATH/.keyboard_info for version 1.0 projects) + + return true; + } + + loadProject(): KeymanDeveloperProject { + // TODO: callbacks.reportMessage on exceptions + // TODO: version 2.0 projects are folder-based and scan source/ folder for valid + // files. .kpj need not exist, but if it does, is used just for options. + + this.infile = path.resolve(this.infile.replace(/\\/g, '/')); + + if(fs.statSync(this.infile).isDirectory()) { + // This is a project folder, look for folder-name.kpj + this.infile = path.join(this.infile, path.basename(this.infile) + '.kpj'); + } + + const project = fs.existsSync(this.infile) ? + this.loadProjectFromFile() : + this.loadDefaultProjectFromFolder(); + + return project; + } + + loadDefaultProjectFromFolder() { + // The folder does not contain a .kpj, so construct a default 2.0 .kpj + const project = new KeymanDeveloperProject('2.0'); + project.populateFiles(this.infile); + return project; + } + + loadProjectFromFile(): KeymanDeveloperProject { + const kpjData = this.callbacks.loadFile(null, this.infile); + const reader = new KPJFileReader(); + const kpj = reader.read(kpjData); + const schema = this.callbacks.loadKpjJsonSchema(); + try { + reader.validate(kpj, schema); + } catch(e) { + // TODO: callbacks.reportMessage + console.error(e); + return null; + } + const project = reader.transform(this.infile, kpj); + return project; + } + + async buildProjectTargets( + buildTarget: (infile: string, options: BuildCommandOptions) => Promise, + fileType: '.xml'|'.kmn'|'.model.ts'|'.kps', + outputFileType: '.kmx'|'.model.js'|'.kmp' + ): Promise { + let result = true; + for(let file of this.project.files) { + if(file.fileType.toLowerCase() == fileType) { + result = await this.buildTarget(file, buildTarget, fileType, outputFileType) && result; + } + } + return result; + } + + async buildTarget( + file: KeymanDeveloperProjectFile, + buildTarget: (infile: string, options: BuildCommandOptions) => Promise, + fileType: '.xml'|'.kmn'|'.model.ts'|'.kps', + outputFileType: '.kmx'|'.model.js'|'.kmp' + ): Promise { + const options = {...this.options}; + options.outFile = this.project.resolveOutputFilePath(this.infile, file, fileType, outputFileType); + const infile = this.project.resolveInputFilePath(this.infile, file); + // TODO: callbacks.reportMessage, improve logging and make consistent + console.log(`Building ${infile}\n Output ${options.outFile}`); + + fs.mkdirSync(path.dirname(options.outFile), {recursive:true}); + + let result = await buildTarget(infile, options); + if(result) { + console.log(`${path.basename(infile )} built successfully.`); + } else { + console.log(`${path.basename(infile)} failed to build.`); + } + return result; + } + +} \ No newline at end of file diff --git a/developer/src/kmc/src/activities/buildTestData.ts b/developer/src/kmc/src/activities/buildTestData.ts new file mode 100644 index 00000000000..d0e2fcfaefe --- /dev/null +++ b/developer/src/kmc/src/activities/buildTestData.ts @@ -0,0 +1,35 @@ +import * as fs from 'fs'; +import * as path from 'path'; +import * as kmc from '@keymanapp/kmc-keyboard'; +import { CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; +import { NodeCompilerCallbacks } from '../util/NodeCompilerCallbacks.js'; +import { BuildCommandOptions } from '../commands/build.js'; + +export function buildTestData(infile: string, options: BuildCommandOptions) { + let compilerOptions: kmc.CompilerOptions = { + debug: false, + addCompilerVersion: false + }; + + let testData = loadTestData(infile, compilerOptions); + if (!testData) { + return; + } + + const fileBaseName = options.outFile ?? infile; + const outFileBase = path.basename(fileBaseName, path.extname(fileBaseName)); + const outFileDir = path.dirname(fileBaseName); + const outFileJson = path.join(outFileDir, outFileBase + '.json'); + console.log(`Writing JSON test data to ${outFileJson}`); + fs + .writeFileSync(outFileJson, JSON.stringify(testData, null, ' ')); +} +function loadTestData(inputFilename: string, options: kmc.CompilerOptions): LDMLKeyboardTestDataXMLSourceFile { + const c: CompilerCallbacks = new NodeCompilerCallbacks(); + const k = new kmc.Compiler(c, options); + let source = k.loadTestData(inputFilename); + if (!source) { + return null; + } + return source; +} diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts new file mode 100644 index 00000000000..857ba4b7379 --- /dev/null +++ b/developer/src/kmc/src/commands/build.ts @@ -0,0 +1,64 @@ +import * as fs from 'fs'; +import { Command } from 'commander'; +import { buildPackage } from '../activities/buildPackage.js'; +import { buildKmnKeyboard } from '../activities/buildKmnKeyboard.js'; +import { buildLdmlKeyboard } from '../activities/buildLdmlKeyboard.js'; +import { buildModel } from '../activities/buildModel.js'; +import { buildProject } from '../activities/buildProject.js'; + +export interface BuildCommandOptions { + debug?: boolean; + outFile?: string; + compilerVersion?: boolean; +}; + +export function declareBuild(program: Command) { + program + .command('build [infile...]') + .description('Build a source file into a final file') + .option('-d, --debug', 'Include debug information in output') + .option('-o, --out-file ', 'where to save the resulting .kmx file') + .option('--no-compiler-version', 'Exclude compiler version metadata from output') + .action((infiles: string[], options: any) => { + let p = []; + if(!infiles.length) { + console.debug('Assuming infile == .'); + p.push(build('.', options)); + } + for(let infile of infiles) { + p.push(build(infile, options)); + } + return Promise.all(p).then(); + }); +} + +async function build(infile: string, options: BuildCommandOptions): Promise { + console.log(`Building ${infile}`); + + if(infile.endsWith('.xml')) { + return buildLdmlKeyboard(infile, options); + } + + if(infile.endsWith('.kmn')) { + return buildKmnKeyboard(infile, options); + } + + if(infile.endsWith('.kps')) { + return buildPackage(infile, options); + } + + if(infile.endsWith('.model.ts')) { + return buildModel(infile, options); + } + + if(infile.endsWith('.kpj')) { + return buildProject(infile, options); + } + + if(fs.statSync(infile).isDirectory()) { + return buildProject(infile, options); + } + + console.error(`Unrecognised input file ${infile}, expecting .xml, .kmn, .kps, .model.ts, .kpj, or project folder`); + process.exit(2); +} diff --git a/developer/src/kmc/src/commands/buildTestData.ts b/developer/src/kmc/src/commands/buildTestData.ts new file mode 100644 index 00000000000..1e087214afe --- /dev/null +++ b/developer/src/kmc/src/commands/buildTestData.ts @@ -0,0 +1,14 @@ + + +import { Command } from 'commander'; +import { buildTestData } from '../activities/buildTestData.js'; + +export function declareBuildTestData(program: Command) { + program + .command('build-test-data ') + .description('Convert keyboard test .xml to .json') + .option('-o, --out-file ', 'where to save the resulting .json file') + .action(buildTestData); +} + + diff --git a/developer/src/kmc/src/kmc.ts b/developer/src/kmc/src/kmc.ts new file mode 100644 index 00000000000..6149d126e23 --- /dev/null +++ b/developer/src/kmc/src/kmc.ts @@ -0,0 +1,47 @@ +#!/usr/bin/env node +/** + * kmc - Keyman Next Generation Compiler + */ + + +import { Command } from 'commander'; +import KEYMAN_VERSION from "@keymanapp/keyman-version/keyman-version.mjs"; +import { declareBuild } from './commands/build.js'; +import { declareBuildTestData } from './commands/buildTestData.js'; + +const program = new Command(); + +/* Arguments */ +program + .description('Keyman Developer Command Line Interface') + .version(KEYMAN_VERSION.VERSION_WITH_TAG); + +declareBuild(program); +declareBuildTestData(program); + +/* +program + .command('clean'); + +program + .command('copy'); + +program + .command('rename'); + +program + .command('generate'); + +program + .command('import'); + +program + .command('test'); + +program + .command('publish'); +*/ + +program.parseAsync(process.argv) + .catch(reason => console.error(reason)); + diff --git a/developer/src/kmlmc/source/kmlmc.ts b/developer/src/kmc/src/kmlmc.ts similarity index 58% rename from developer/src/kmlmc/source/kmlmc.ts rename to developer/src/kmc/src/kmlmc.ts index 659565a8f48..c700dc7fcd0 100644 --- a/developer/src/kmlmc/source/kmlmc.ts +++ b/developer/src/kmc/src/kmlmc.ts @@ -2,17 +2,15 @@ /** * kmlmc - Keyman Lexical Model Compiler */ -/// import * as fs from 'fs'; -import * as program from 'commander'; - -import { compileModel } from './util/util'; +import { Command } from 'commander'; +import { compileModel } from '@keymanapp/kmc-model'; import { SysExits } from './util/sysexits'; +import KEYMAN_VERSION from "@keymanapp/keyman-version/keyman-version.mjs"; let inputFilename: string; - -const KEYMAN_VERSION = require("@keymanapp/keyman-version").KEYMAN_VERSION; +const program = new Command(); /* Arguments */ program @@ -29,18 +27,29 @@ if (!inputFilename) { exitDueToUsageError('Must provide a lexical model source file.'); } +let code = null; // Compile: -let code = compileModel(inputFilename); +try { + code = compileModel(inputFilename); +} catch(e) { + console.error(e); + process.exit(SysExits.EX_DATAERR); +} + +if(!code) { + console.error('Compilation failed.') + process.exit(SysExits.EX_DATAERR); +} // Output: -if (program.outFile) { - fs.writeFileSync(program.outFile, code, 'utf8'); +if (program.opts().outFile) { + fs.writeFileSync(program.opts().outFile, code, 'utf8'); } else { console.log(code); } function exitDueToUsageError(message: string): never { - console.error(`${program._name}: ${message}`); + console.error(`${program.name()}: ${message}`); console.error(); program.outputHelp(); return process.exit(SysExits.EX_USAGE); diff --git a/developer/src/kmlmc/source/kmlmi.ts b/developer/src/kmc/src/kmlmi.ts similarity index 57% rename from developer/src/kmlmc/source/kmlmi.ts rename to developer/src/kmc/src/kmlmi.ts index fc8534f5db4..5354124fa16 100644 --- a/developer/src/kmlmc/source/kmlmi.ts +++ b/developer/src/kmc/src/kmlmi.ts @@ -3,15 +3,16 @@ * kmlmi - Keyman Lexical Model model_info Compiler */ -import * as program from 'commander'; import * as fs from 'fs'; import * as path from 'path'; -import KmpCompiler from './package-compiler/kmp-compiler'; -import { ModelInfoOptions as ModelInfoOptions, writeMergedModelMetadataFile } from './model-info-compiler/model-info-compiler'; +import { Command } from 'commander'; +import KmpCompiler from '@keymanapp/kmc-package'; +import { ModelInfoOptions as ModelInfoOptions, writeMergedModelMetadataFile } from '@keymanapp/kmc-model-info'; import { SysExits } from './util/sysexits'; -const KEYMAN_VERSION = require("@keymanapp/keyman-version").KEYMAN_VERSION; +import KEYMAN_VERSION from "@keymanapp/keyman-version/keyman-version.mjs"; let inputFilename: string; +const program = new Command(); /* Arguments */ program @@ -34,11 +35,11 @@ if (!inputFilename) { exitDueToUsageError('Must provide a lexical model .model_info source file.'); } -let model_id: string = program.model ? program.model : path.basename(inputFilename).replace(/\.model_info$/, ""); -let outputFilename: string = program.outFile ? program.outFile : path.join(path.dirname(inputFilename), 'build', path.basename(inputFilename)); -let kpsFilename = program.kpsFilename ? program.kpsFilename : path.join(path.dirname(inputFilename), 'source', path.basename(inputFilename).replace(/\.model_info$/, '.model.kps')); -let kmpFilename = program.kmpFilename ? program.kmpFilename : path.join(path.dirname(inputFilename), 'build', path.basename(inputFilename).replace(/\.model_info$/, '.model.kmp')); -let jsFilename = program.jsFilename ? program.jsFilename : path.join(path.dirname(inputFilename), 'build', path.basename(inputFilename).replace(/\.model_info$/, '.model.js')); +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 @@ -55,18 +56,23 @@ let kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString, kpsFilename); let modelInfoOptions: ModelInfoOptions = { model_id: model_id, kmpJsonData: kmpJsonData, - sourcePath: program.source, + sourcePath: program.opts().source, modelFileName: jsFilename, kmpFileName: kmpFilename }; -writeMergedModelMetadataFile( - inputFilename, - outputFilename, - modelInfoOptions); +try { + writeMergedModelMetadataFile( + inputFilename, + outputFilename, + modelInfoOptions); +} catch(e) { + console.error(e); + process.exit(SysExits.EX_DATAERR); +} function exitDueToUsageError(message: string): never { - console.error(`${program._name}: ${message}`); + console.error(`${program.name()}: ${message}`); console.error(); program.outputHelp(); return process.exit(SysExits.EX_USAGE); diff --git a/developer/src/kmlmc/source/kmlmp.ts b/developer/src/kmc/src/kmlmp.ts similarity index 73% rename from developer/src/kmlmc/source/kmlmp.ts rename to developer/src/kmc/src/kmlmp.ts index eb3c59db4e9..6cb4174d82d 100644 --- a/developer/src/kmlmc/source/kmlmp.ts +++ b/developer/src/kmc/src/kmlmp.ts @@ -3,13 +3,14 @@ * kmlmp - Keyman Lexical Model Package Compiler */ -import * as program from 'commander'; import * as fs from 'fs'; -import KmpCompiler from './package-compiler/kmp-compiler'; +import { Command } from 'commander'; +import KmpCompiler from '@keymanapp/kmc-package'; import { SysExits } from './util/sysexits'; -const KEYMAN_VERSION = require("@keymanapp/keyman-version").KEYMAN_VERSION; +import KEYMAN_VERSION from "@keymanapp/keyman-version/keyman-version.mjs"; let inputFilename: string; +const program = new Command(); /* Arguments */ program @@ -27,7 +28,7 @@ if (!inputFilename) { exitDueToUsageError('Must provide a lexical model package source file.'); } -let outputFilename: string = program.outFile ? program.outFile : inputFilename.replace(/\.kps$/, ".kmp"); +let outputFilename: string = program.opts().outFile ? program.opts().outFile : inputFilename.replace(/\.kps$/, ".kmp"); // // Load .kps source data @@ -41,7 +42,7 @@ let kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString, inputFilename); // Build the .kmp package file // -let promise = kmpCompiler.buildKmpFile(kmpJsonData); +let promise = kmpCompiler.buildKmpFile(inputFilename, kmpJsonData); promise.then(data => { fs.writeFileSync(outputFilename, data, 'binary'); }).catch(error => { @@ -50,7 +51,7 @@ promise.then(data => { }); function exitDueToUsageError(message: string): never { - console.error(`${program._name}: ${message}`); + console.error(`${program.name()}: ${message}`); console.error(); program.outputHelp(); return process.exit(SysExits.EX_USAGE); diff --git a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts new file mode 100644 index 00000000000..a777b0a7d3c --- /dev/null +++ b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts @@ -0,0 +1,40 @@ +import * as fs from 'fs'; +import * as kmc from '@keymanapp/kmc-keyboard'; +import { CompilerCallbacks, CompilerEvent } from '@keymanapp/common-types'; + +/** + * Concrete implementation for CLI use + */ +export class NodeCompilerCallbacks implements CompilerCallbacks { + loadFile(baseFilename: string, filename: string | URL): Buffer { + // TODO: translate filename based on the baseFilename + try { + return fs.readFileSync(filename); + } catch (e) { + if (e.code === 'ENOENT') { + return null; + } else { + throw e; + } + } + } + reportMessage(event: CompilerEvent): void { + console.log(kmc.CompilerMessages.severityName(event.code) + ' ' + event.code.toString(16) + ': ' + event.message); + } + loadLdmlKeyboardSchema(): Buffer { + let schemaPath = new URL('ldml-keyboard.schema.json', import.meta.url); + return fs.readFileSync(schemaPath); + } + loadLdmlKeyboardTestSchema(): Buffer { + let schemaPath = new URL('ldml-keyboardtest.schema.json', import.meta.url); + return fs.readFileSync(schemaPath); + } + loadKvksJsonSchema(): Buffer { + let schemaPath = new URL('kvks.schema.json', import.meta.url); + return fs.readFileSync(schemaPath); + } + loadKpjJsonSchema(): Buffer { + let schemaPath = new URL('kpj.schema.json', import.meta.url); + return fs.readFileSync(schemaPath); + } +} diff --git a/developer/src/kmc/src/util/getDeveloperBinPath.ts b/developer/src/kmc/src/util/getDeveloperBinPath.ts new file mode 100644 index 00000000000..e59e6ac92f3 --- /dev/null +++ b/developer/src/kmc/src/util/getDeveloperBinPath.ts @@ -0,0 +1,38 @@ +import { fileURLToPath } from 'url'; +import * as path from 'path'; +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 + * parent folder recursively until we reach the root of the filesystem. + * @returns string | null + */ +export function getDeveloperBinPath(): string { + // if running in source repo, then we always look in developer/bin/ + const keymanRoot = process.env['KEYMAN_ROOT']; + if (keymanRoot) { + // https://stackoverflow.com/a/45242825/1836776 (imperfect due to + // possibility of '..foo', but good enough) + const rel = path.relative(keymanRoot, import.meta.url); + if (rel && !rel.startsWith('..') && !path.isAbsolute(rel)) { + return path.join(keymanRoot, 'developer', 'bin'); + } + } + + // 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? + 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'))) { + return p; + } + p = path.dirname(p); + } while (p.toLowerCase() != root); + + // kmcomp.exe was not found on the path + return null; +} diff --git a/developer/src/kmlmc/source/util/sysexits.ts b/developer/src/kmc/src/util/sysexits.ts similarity index 100% rename from developer/src/kmlmc/source/util/sysexits.ts rename to developer/src/kmc/src/util/sysexits.ts diff --git a/developer/src/kmc/test/fixtures/.gitignore b/developer/src/kmc/test/fixtures/.gitignore new file mode 100644 index 00000000000..13bb51d0488 --- /dev/null +++ b/developer/src/kmc/test/fixtures/.gitignore @@ -0,0 +1 @@ +*/*/build \ No newline at end of file diff --git a/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.ico b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.ico new file mode 100644 index 00000000000..42c90c398da Binary files /dev/null and b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.ico differ diff --git a/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.keyman-touch-layout b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.keyman-touch-layout new file mode 100644 index 00000000000..711f8490083 --- /dev/null +++ b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.keyman-touch-layout @@ -0,0 +1,1950 @@ +{ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "១" + }, + { + "id": "K_2", + "text": "២" + }, + { + "id": "K_3", + "text": "៣" + }, + { + "id": "K_4", + "text": "៤" + }, + { + "id": "K_5", + "text": "៥" + }, + { + "id": "K_6", + "text": "៦" + }, + { + "id": "K_7", + "text": "៧" + }, + { + "id": "K_8", + "text": "៨" + }, + { + "id": "K_9", + "text": "៩" + }, + { + "id": "K_0", + "text": "០" + }, + { + "id": "K_HYPHEN", + "text": "ឥ" + }, + { + "id": "K_EQUAL", + "text": "ឲ" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "រ" + }, + { + "id": "K_T", + "text": "ត" + }, + { + "id": "K_Y", + "text": "យ" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ផ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឪ" + }, + { + "id": "T_new_138", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "«" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "ស" + }, + { + "id": "K_D", + "text": "ដ" + }, + { + "id": "K_F", + "text": "ថ" + }, + { + "id": "K_G", + "text": "ង" + }, + { + "id": "K_H", + "text": "ហ" + }, + { + "id": "K_J", + "text": "" + }, + { + "id": "K_K", + "text": "ក" + }, + { + "id": "K_L", + "text": "ល" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឮ" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "ឋ" + }, + { + "id": "K_X", + "text": "ខ" + }, + { + "id": "K_C", + "text": "ច" + }, + { + "id": "K_V", + "text": "វ" + }, + { + "id": "K_B", + "text": "ប" + }, + { + "id": "K_N", + "text": "ន" + }, + { + "id": "K_M", + "text": "ម" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "។" + }, + { + "id": "K_SLASH", + "text": "" + }, + { + "id": "T_new_164", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "rightalt", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "‌" + }, + { + "id": "K_2", + "text": "@" + }, + { + "id": "K_3", + "text": "" + }, + { + "id": "K_4", + "text": "$" + }, + { + "id": "K_5", + "text": "€" + }, + { + "id": "K_6", + "text": "៙" + }, + { + "id": "K_7", + "text": "៚" + }, + { + "id": "K_8", + "text": "*" + }, + { + "id": "K_9", + "text": "{" + }, + { + "id": "K_0", + "text": "}" + }, + { + "id": "K_HYPHEN", + "text": "≈" + }, + { + "id": "K_EQUAL", + "text": "" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ៜ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "ឯ" + }, + { + "id": "K_R", + "text": "ឫ" + }, + { + "id": "K_T", + "text": "ឨ" + }, + { + "id": "K_Y", + "text": "[" + }, + { + "id": "K_U", + "text": "]" + }, + { + "id": "K_I", + "text": "ឦ" + }, + { + "id": "K_O", + "text": "ឱ" + }, + { + "id": "K_P", + "text": "ឰ" + }, + { + "id": "K_LBRKT", + "text": "ឩ" + }, + { + "id": "K_RBRKT", + "text": "ឳ" + }, + { + "id": "T_new_307", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "‍" + }, + { + "id": "K_A", + "text": "+" + }, + { + "id": "K_S", + "text": "-" + }, + { + "id": "K_D", + "text": "×" + }, + { + "id": "K_F", + "text": "÷" + }, + { + "id": "K_G", + "text": ":" + }, + { + "id": "K_H", + "text": "‘" + }, + { + "id": "K_J", + "text": "’" + }, + { + "id": "K_K", + "text": "ឝ" + }, + { + "id": "K_L", + "text": "៘" + }, + { + "id": "K_COLON", + "text": "៖" + }, + { + "id": "K_QUOTE", + "text": "ៈ" + }, + { + "id": "K_BKSLASH", + "text": "\\" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "<" + }, + { + "id": "K_X", + "text": ">" + }, + { + "id": "K_C", + "text": "#" + }, + { + "id": "K_V", + "text": "&" + }, + { + "id": "K_B", + "text": "ឞ" + }, + { + "id": "K_N", + "text": ";" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "," + }, + { + "id": "K_PERIOD", + "text": "." + }, + { + "id": "K_SLASH", + "text": "/" + }, + { + "id": "T_new_333", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": " ", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "!" + }, + { + "id": "K_2", + "text": "ៗ" + }, + { + "id": "K_3", + "text": "\"" + }, + { + "id": "K_4", + "text": "៛" + }, + { + "id": "K_5", + "text": "%" + }, + { + "id": "K_6", + "text": "" + }, + { + "id": "K_7", + "text": "" + }, + { + "id": "K_8", + "text": "" + }, + { + "id": "K_9", + "text": "(" + }, + { + "id": "K_0", + "text": ")" + }, + { + "id": "K_HYPHEN", + "text": "" + }, + { + "id": "K_EQUAL", + "text": "=" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ឈ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "ឬ" + }, + { + "id": "K_T", + "text": "ទ" + }, + { + "id": "K_Y", + "text": "" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ភ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឧ" + }, + { + "id": "T_new_364", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "»" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "" + }, + { + "id": "K_D", + "text": "ឌ" + }, + { + "id": "K_F", + "text": "ធ" + }, + { + "id": "K_G", + "text": "អ" + }, + { + "id": "K_H", + "text": "ះ" + }, + { + "id": "K_J", + "text": "ញ" + }, + { + "id": "K_K", + "text": "គ" + }, + { + "id": "K_L", + "text": "ឡ" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឭ" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "ឍ" + }, + { + "id": "K_X", + "text": "ឃ" + }, + { + "id": "K_C", + "text": "ជ" + }, + { + "id": "K_V", + "text": "" + }, + { + "id": "K_B", + "text": "ព" + }, + { + "id": "K_N", + "text": "ណ" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "៕" + }, + { + "id": "K_SLASH", + "text": "?" + }, + { + "id": "T_new_390", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + } + ], + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + }, + "phone": { + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "", + "sk": [ + { + "text": "ឈ", + "id": "K_Q", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1786" + }, + { + "text": "", + "id": "T_17D2_1788" + } + ] + }, + { + "id": "K_W", + "text": "", + "sk": [ + { + "text": "", + "id": "K_W", + "layer": "shift" + } + ] + }, + { + "id": "K_E", + "text": "", + "sk": [ + { + "text": "", + "id": "K_E", + "layer": "shift" + }, + { + "text": "", + "id": "K_S", + "layer": "shift" + }, + { + "text": "", + "id": "K_V", + "layer": "shift" + }, + { + "text": "ឯ", + "id": "U_17AF" + }, + { + "text": "ឰ", + "id": "U_17B0" + } + ] + }, + { + "id": "K_R", + "text": "រ", + "sk": [ + { + "text": "", + "id": "T_17D2_179A" + }, + { + "text": "ឫ", + "id": "U_17AB" + }, + { + "text": "ឬ", + "id": "U_17AC" + } + ] + }, + { + "id": "K_T", + "text": "ត", + "sk": [ + { + "text": "ទ", + "id": "K_T", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178F" + }, + { + "text": "", + "id": "T_17D2_1791", + "layer": "default" + } + ] + }, + { + "id": "K_Y", + "text": "យ", + "sk": [ + { + "text": "", + "id": "T_17D2_1799" + } + ] + }, + { + "id": "K_U", + "text": "", + "sk": [ + { + "text": "", + "id": "K_U", + "layer": "shift" + }, + { + "text": "", + "id": "K_Y", + "layer": "shift" + }, + { + "text": "ឧ", + "id": "U_17A7" + }, + { + "text": "ឪ", + "id": "U_17AA", + "layer": "shift" + }, + { + "text": "ឩ", + "id": "U_17A9", + "layer": "shift" + }, + { + "text": "ឨ", + "id": "U_17A8" + } + ] + }, + { + "id": "K_I", + "text": "", + "sk": [ + { + "text": "", + "id": "K_I", + "layer": "shift" + }, + { + "text": "ឥ", + "id": "U_17A5" + }, + { + "text": "ឦ", + "id": "U_17A6", + "layer": "shift" + } + ] + }, + { + "id": "K_O", + "text": "", + "sk": [ + { + "text": "", + "id": "K_O", + "layer": "shift" + }, + { + "text": "", + "id": "K_LBRKT" + }, + { + "text": "", + "id": "K_LBRKT", + "layer": "shift" + }, + { + "text": "", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": "ឱ", + "id": "U_17B1" + }, + { + "text": "ឲ", + "id": "U_17B2" + }, + { + "text": "ឳ", + "id": "U_17B3", + "layer": "shift" + } + ] + }, + { + "id": "K_P", + "text": "ផ", + "sk": [ + { + "text": "ភ", + "id": "K_P", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1795" + }, + { + "text": "", + "id": "T_17D2_1797", + "layer": "default" + } + ] + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "", + "pad": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_A", + "layer": "shift" + } + ] + }, + { + "id": "K_S", + "text": "ស", + "sk": [ + { + "text": "", + "id": "T_17D2_179F" + }, + { + "text": "ឝ", + "id": "U_179D" + }, + { + "text": "ឞ", + "id": "U_179E" + } + ] + }, + { + "id": "K_D", + "text": "ដ", + "sk": [ + { + "text": "ឌ", + "id": "K_D", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178A" + }, + { + "text": "", + "id": "T_17D2_178C", + "layer": "default" + } + ] + }, + { + "id": "K_F", + "text": "ថ", + "sk": [ + { + "text": "ធ", + "id": "K_F", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1790" + }, + { + "text": "", + "id": "T_17D2_1792", + "layer": "default" + } + ] + }, + { + "id": "K_G", + "text": "ង", + "sk": [ + { + "text": "អ", + "id": "K_G", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1784" + }, + { + "text": "", + "id": "T_17D2_17A2", + "layer": "default" + } + ] + }, + { + "id": "K_H", + "text": "ហ", + "sk": [ + { + "text": "", + "id": "T_17D2_17A0" + }, + { + "text": "ះ", + "id": "K_H", + "layer": "shift" + }, + { + "text": "ៈ", + "id": "U_17C8" + } + ] + }, + { + "id": "K_J", + "text": "ញ", + "layer": "shift", + "sk": [ + { + "text": "", + "id": "T_17D2_1789" + } + ] + }, + { + "id": "K_K", + "text": "ក", + "sk": [ + { + "text": "គ", + "id": "K_K", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1780" + }, + { + "text": "", + "id": "T_17D2_1782" + } + ] + }, + { + "id": "K_L", + "text": "ល", + "sk": [ + { + "text": "ឡ", + "id": "K_L", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_179B" + }, + { + "text": "ឭ", + "id": "U_17AD" + }, + { + "text": "ឮ", + "id": "U_17AE" + } + ] + }, + { + "id": "K_COLON", + "text": "" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_Z", + "text": "ឋ", + "pad": "", + "width": "", + "sk": [ + { + "text": "ឍ", + "id": "K_Z", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178B" + }, + { + "text": "", + "id": "T_17D2_178D", + "layer": "default" + } + ] + }, + { + "id": "K_X", + "text": "ខ", + "sk": [ + { + "text": "ឃ", + "id": "K_X", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1781" + }, + { + "text": "", + "id": "T_17D2_1783", + "layer": "default" + } + ] + }, + { + "id": "K_C", + "text": "ច", + "sk": [ + { + "text": "ជ", + "id": "K_C", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1785" + }, + { + "text": "", + "id": "T_17D2_1787", + "layer": "default" + } + ] + }, + { + "id": "K_V", + "text": "វ", + "sk": [ + { + "text": "", + "id": "T_17D2_179C" + } + ] + }, + { + "id": "K_B", + "text": "ប", + "sk": [ + { + "text": "ព", + "id": "K_B", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1794" + }, + { + "text": "", + "id": "T_17D2_1796", + "layer": "default" + } + ] + }, + { + "id": "K_N", + "text": "ន", + "sk": [ + { + "text": "ណ", + "id": "K_N", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1793" + }, + { + "text": "", + "id": "T_17D2_178E", + "layer": "default" + } + ] + }, + { + "id": "K_M", + "text": "ម", + "sk": [ + { + "text": "", + "id": "T_17D2_1798" + }, + { + "text": "", + "id": "K_M", + "layer": "shift" + } + ] + }, + { + "id": "K_COMMA", + "text": "", + "sk": [ + { + "text": "", + "id": "K_COMMA", + "layer": "shift" + }, + { + "text": "", + "id": "K_6", + "layer": "shift" + }, + { + "text": "", + "id": "K_7", + "layer": "shift" + }, + { + "text": "", + "id": "K_8", + "layer": "shift" + }, + { + "text": "", + "id": "K_HYPHEN", + "layer": "shift" + }, + { + "text": "", + "id": "U_17D1", + "layer": "shift" + }, + { + "text": "", + "id": "U_17DD", + "layer": "shift" + }, + { + "text": "", + "id": "U_17CE", + "layer": "shift" + } + ] + }, + { + "id": "K_QUOTE", + "text": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "", + "id": "K_SLASH" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "១២៣", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "sp": "0", + "sk": [ + { + "text": " ", + "id": "U_0020", + "layer": "default" + } + ] + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "១", + "pad": "", + "sk": [ + { + "text": "1", + "id": "U_0031" + } + ] + }, + { + "id": "K_2", + "text": "២", + "sk": [ + { + "text": "2", + "id": "U_0032" + } + ] + }, + { + "id": "K_3", + "text": "៣", + "sk": [ + { + "text": "3", + "id": "U_0033" + } + ] + }, + { + "id": "K_4", + "text": "៤", + "sk": [ + { + "text": "4", + "id": "U_0034" + } + ] + }, + { + "id": "K_5", + "text": "៥", + "sk": [ + { + "text": "5", + "id": "U_0035" + } + ] + }, + { + "id": "K_6", + "text": "៦", + "sk": [ + { + "text": "6", + "id": "U_0036" + } + ] + }, + { + "id": "K_7", + "text": "៧", + "sk": [ + { + "text": "7", + "id": "U_0037" + } + ] + }, + { + "id": "K_8", + "text": "៨", + "sk": [ + { + "text": "8", + "id": "U_0038" + } + ] + }, + { + "id": "K_9", + "text": "៩", + "sk": [ + { + "text": "9", + "id": "U_0039" + } + ] + }, + { + "id": "K_0", + "text": "០", + "sk": [ + { + "text": "0", + "id": "U_0030" + }, + { + "text": "", + "id": "U_17D3" + } + ] + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "U_0040", + "text": "@", + "pad": "", + "sk": [ + { + "text": "©", + "id": "U_00A9" + }, + { + "text": "®", + "id": "U_00AE" + } + ] + }, + { + "id": "U_0023", + "text": "#", + "sk": [ + { + "text": "№", + "id": "U_2116" + }, + { + "text": "~", + "id": "U_007E" + } + ] + }, + { + "id": "U_17DB", + "text": "៛", + "sk": [ + { + "text": "$", + "id": "U_0024" + }, + { + "text": "฿", + "id": "U_0E3F" + }, + { + "text": "¢", + "id": "U_00A2" + }, + { + "text": "£", + "id": "U_00A3" + }, + { + "text": "¥", + "id": "U_00A5" + } + ] + }, + { + "id": "U_0026", + "text": "&" + }, + { + "id": "U_0025", + "text": "%", + "sk": [ + { + "text": "‰", + "id": "U_2030" + }, + { + "text": "‱", + "id": "U_2031" + } + ] + }, + { + "id": "U_002B", + "text": "+", + "sk": [ + { + "text": "-", + "id": "U_002D" + }, + { + "text": "×", + "id": "U_00D7" + }, + { + "text": "÷", + "id": "U_00F7" + }, + { + "text": "±", + "id": "U_00B1" + } + ] + }, + { + "id": "U_003D", + "text": "=", + "sk": [ + { + "text": "_", + "id": "U_005F" + }, + { + "text": "≠", + "id": "U_2260" + } + ] + }, + { + "id": "U_002A", + "text": "*", + "sk": [ + { + "text": "^", + "id": "U_005E" + } + ] + }, + { + "id": "U_003F", + "text": "?", + "sk": [ + { + "text": "¿", + "id": "U_00BF" + } + ] + }, + { + "id": "U_0021", + "text": "!", + "sk": [ + { + "text": "¡", + "id": "U_00A1" + } + ] + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "U_2018", + "text": "‘", + "sk": [ + { + "text": "’", + "id": "U_2019" + } + ] + }, + { + "id": "U_201C", + "text": "“", + "sk": [ + { + "text": "”", + "id": "U_201D" + } + ] + }, + { + "id": "U_00AB", + "text": "«", + "pad": "", + "sk": [ + { + "text": "»", + "id": "U_00BB" + } + ] + }, + { + "id": "U_002F", + "text": "/", + "sk": [ + { + "text": "\\", + "id": "U_005C" + }, + { + "text": "|", + "id": "U_007C" + }, + { + "text": "¦", + "id": "U_00A6" + } + ] + }, + { + "id": "U_0028", + "text": "(", + "sk": [ + { + "text": ")", + "id": "U_0029" + }, + { + "text": "[", + "id": "U_005B" + }, + { + "text": "]", + "id": "U_005D" + }, + { + "text": "{", + "id": "U_007B" + }, + { + "text": "}", + "id": "U_007D" + } + ] + }, + { + "id": "U_17D9", + "text": "៙", + "sk": [ + { + "text": "៚", + "id": "U_17DA" + }, + { + "text": "ៜ", + "id": "U_17DC" + }, + { + "text": "§", + "id": "U_00A7" + }, + { + "text": "Ø", + "id": "U_00D8" + } + ] + }, + { + "id": "U_17D7", + "text": "ៗ" + }, + { + "id": "U_003C", + "text": "<", + "sk": [ + { + "text": "≤", + "id": "U_2264" + }, + { + "text": ">", + "id": "U_003E" + }, + { + "text": "≥", + "id": "U_2265" + } + ] + }, + { + "id": "U_17D6", + "text": "៖", + "sk": [ + { + "text": ":", + "id": "U_003A" + }, + { + "text": ";", + "id": "U_003B" + }, + { + "text": "…", + "id": "U_2026" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LCONTROL", + "text": "១២៣", + "width": "140", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "sp": "0", + "layer": "shift", + "sk": [] + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + } + ], + "displayUnderlying": false, + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + } +} \ No newline at end of file diff --git a/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kmn b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kmn new file mode 100644 index 00000000000..b7919afca3e --- /dev/null +++ b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kmn @@ -0,0 +1,642 @@ +store(&VERSION) '10.0' +store(&NAME) "Khmer Angkor" +store(©RIGHT) '© 2015-2022 SIL International' +store(&MESSAGE) "More than just a Khmer Unicode keyboard." +store(&TARGETS) 'any' +store(&LAYOUTFILE) 'khmer_angkor.keyman-touch-layout' +store(&KEYBOARDVERSION) '1.3' +store(&BITMAP) 'khmer_angkor.ico' +store(&VISUALKEYBOARD) 'khmer_angkor.kvks' + +begin Unicode > use(main) + + + +c ==============================================STORES============================================== + + + +c 33 consonants and two deprecated consonants + +store(c_key) [K_K] [K_X] [SHIFT K_K] [SHIFT K_X] [K_G] \ + [K_C] [K_Q] [SHIFT K_C] [SHIFT K_Q] [SHIFT K_J] \ + [K_D] [K_Z] [SHIFT K_D] [SHIFT K_Z] [SHIFT K_N] \ + [K_T] [K_F] [SHIFT K_T] [SHIFT K_F] [K_N] \ + [K_B] [K_P] [SHIFT K_B] [SHIFT K_P] [K_M] \ + [K_Y] [K_R] [K_L] [K_V] [K_S] [K_H] [SHIFT K_L] [SHIFT K_G] \ + [RALT K_K] [RALT K_B] +store(c_out) U+1780 U+1781 U+1782 U+1783 U+1784 \ + U+1785 U+1786 U+1787 U+1788 U+1789 \ + U+178A U+178B U+178C U+178D U+178E \ + U+178F U+1790 U+1791 U+1792 U+1793 \ + U+1794 U+1795 U+1796 U+1797 U+1798 \ + U+1799 U+179A U+179B U+179C U+179F U+17A0 U+17A1 U+17A2 \ + U+179D U+179E c deprecated, but they are used in minority languages + +c All genuine dependent vowels + +store(v_gen_key) [K_A] [K_I] [SHIFT K_I] [K_W] [SHIFT K_W] [K_U] [SHIFT K_U] [SHIFT K_Y] \ + [K_COLON] [SHIFT K_LBRKT] [K_LBRKT] [K_E] [SHIFT K_E] [SHIFT K_S] [K_O] [SHIFT K_O] +store(v_gen) U+17B6 U+17B7 U+17B8 U+17B9 U+17BA U+17BB U+17BC U+17BD \ + U+17BE U+17BF U+17C0 U+17C1 U+17C2 U+17C3 U+17C4 U+17C5 + +c All pseudo dependent vowels + +store(v_pseudo_key) [SHIFT K_M] [SHIFT K_H] [RALT K_QUOTE] +store(v_pseudo) U+17C6 U+17C7 U+17C8 + +c Both genuine and pseudo vowels + +store(v_key) outs(v_gen_key) outs(v_pseudo_key) +store(v_out) outs(v_gen) outs(v_pseudo) +store(v_any) outs(v_out) + +c These seven vowels can be concatenated with Reahmuk (U+17C7) to make េះ ោះ ុះ ិះ ីះ ឹះ and ែះ. + +store(v_combo_R) U+17C1 U+17C4 U+17BB U+17B7 U+17B8 U+17B9 U+17C2 + +c these two vowels can be concatenated with Nikahit (U+17C6) to make ាំ and ុំ. + +store(v_combo_N) U+17B6 U+17BB +store(v_combo) outs(v_combo_R) outs(v_combo_N) + +c Independent vowels + +store(ind_v_key) [K_HYPHEN] [RALT K_I] [SHIFT K_RBRKT] [RALT K_T] [RALT K_LBRKT] [K_RBRKT] [RALT K_R] [SHIFT K_R] \ + [SHIFT K_BKSLASH] [K_BKSLASH] [RALT K_E] [RALT K_P] [RALT K_O] [K_EQUAL] [RALT K_RBRKT] +store(ind_v_out) U+17A5 U+17A6 U+17A7 U+17A8 U+17A9 U+17AA U+17AB U+17AC \ + U+17AD U+17AE U+17AF U+17B0 U+17B1 U+17B2 U+17B3 + +c Diacritics + +store(diacritic_key) [K_QUOTE] [SHIFT K_7] [SHIFT K_HYPHEN] [SHIFT K_8] [SHIFT K_6] [RALT K_QUOTE] \ + [RALT K_EQUAL] [RALT K_3] [RALT K_W] [RALT K_Q] [K_J] +store(diacritic_out) U+17CB U+17D0 U+17CC U+17CF U+17CD U+17C8 \ + U+17CE U+17D1 U+17DD U+17DC U+17D2 + +c Consonant shifter--Muusikatoan(U+17C9) and Triisap(U+17CA) + +store(c_shifter_key) [SHIFT K_QUOTE] [K_SLASH] +store(c_shifter) U+17C9 U+17CA + +c punctuations + +store(punct_key) [K_PERIOD] [SHIFT K_PERIOD] [RALT K_COLON] [SHIFT K_2] [RALT K_L] [RALT K_6] [RALT K_7] [RALT K_M] +store(punct_out) U+17D4 U+17D5 U+17D6 U+17D7 U+17D8 U+17D9 U+17DA U+17D3 + +store(latin_punct_key) [K_BKQUOTE] [SHIFT K_BKQUOTE] [SHIFT K_9] [SHIFT K_0] [SHIFT K_1] [SHIFT K_3] \ + [SHIFT K_5] [SHIFT K_EQUAL] [SHIFT K_SLASH] [RALT K_9] [RALT K_0] [RALT K_BKSLASH] \ + [RALT K_2] [RALT K_8] [RALT K_COMMA] [RALT K_D] [RALT K_PERIOD] [RALT K_SLASH] \ + [RALT K_Y] [RALT K_U] [RALT K_BKQUOTE] [RALT K_1] [RALT K_A] [RALT K_S] \ + [RALT K_F] [RALT K_G] [RALT K_HYPHEN] [RALT K_H] [RALT K_J] [RALT K_N] \ + [RALT K_Z] [RALT K_X] [RALT K_C] [RALT K_V] +store(latin_punct_out) U+00AB U+00BB U+0028 U+0029 U+0021 U+0022 \ + U+0025 U+003D U+003F U+007B U+007D U+005C \ + U+0040 U+002A U+002C U+00D7 U+002E U+002F \ + U+005B U+005D U+200D U+200C U+002B U+002D \ + U+00F7 U+003A U+2248 U+2018 U+2019 U+003B \ + U+003C U+003E U+0023 U+0026 +c white space, ZWSP, ZWJ +store(spaces_key) [K_SPACE] [SHIFT K_SPACE] [RALT K_SPACE] +store(spaces_out) U+200B U+0020 U+00A0 + +c Currency: Riel, Dollar and Euro + +store(currency_key) [SHIFT K_4] [RALT K_4] [RALT K_5] +store(currency_out) U+17DB U+0024 U+20AC + +c Khmer digits 0 through 9 + +store(digit_key) [K_0] [K_1] [K_2] [K_3] [K_4] [K_5] [K_6] [K_7] [K_8] [K_9] +store(digit_out) U+17E0 U+17E1 U+17E2 U+17E3 U+17E4 U+17E5 U+17E6 U+17E7 U+17E8 U+17E9 + +c Khmer Lek Attak (a.k.a. divination lore) +store(lek_attak_key) [SHIFT RALT K_0] [SHIFT RALT K_1] [SHIFT RALT K_2] [SHIFT RALT K_3] [SHIFT RALT K_4] \ + [SHIFT RALT K_5] [SHIFT RALT K_6] [SHIFT RALT K_7] [SHIFT RALT K_8] [SHIFT RALT K_9] +store(lek_attak_out) U+17F0 U+17F1 U+17F2 U+17F3 U+17F4 \ + U+17F5 U+17F6 U+17F7 U+17F8 U+17F9 + +c Lunar date format (Khmer style) + +store(lunar_date_key) [SHIFT RALT K_A] [SHIFT RALT K_B] [SHIFT RALT K_C] [SHIFT RALT K_D] [SHIFT RALT K_E] \ + [SHIFT RALT K_F] [SHIFT RALT K_G] [SHIFT RALT K_H] [SHIFT RALT K_I] [SHIFT RALT K_J] \ + [SHIFT RALT K_K] [SHIFT RALT K_L] [SHIFT RALT K_M] [SHIFT RALT K_N] [SHIFT RALT K_O] \ + [SHIFT RALT K_P] [SHIFT RALT K_Q] [SHIFT RALT K_R] [SHIFT RALT K_S] [SHIFT RALT K_T] \ + [SHIFT RALT K_U] [SHIFT RALT K_V] [SHIFT RALT K_W] [SHIFT RALT K_X] [SHIFT RALT K_Y] \ + [SHIFT RALT K_Z] [SHIFT RALT K_COLON] [SHIFT RALT K_COMMA] [SHIFT RALT K_PERIOD] [SHIFT RALT K_LBRKT] \ + [SHIFT RALT K_RBRKT] [SHIFT RALT K_QUOTE] +store(lunar_date_out) U+19EC U+19FB U+19F9 U+19EE U+19E2 \ + U+19EF U+19F0 U+19F1 U+19E7 U+19F2 \ + U+19F3 U+19F4 U+19FD U+19FC U+19E8 \ + U+19E9 U+19E0 U+19E3 U+19ED U+19E4 \ + U+19E6 U+19FA U+19E1 U+19F8 U+19E5 \ + U+19F7 U+19F5 U+19FE U+19FF U+19EA \ + U+19EB U+19F6 + +c Subscripts​ for mobile/table layouts + +store(input_subcons) \ + [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] + +store(subcons) \ + U+1780 U+1781 U+1782 U+1783 U+1784 U+1785 U+1786 U+1787 \ + U+1788 U+1789 U+178A U+178B U+178C U+178D U+178E U+178F \ + U+1790 U+1791 U+1792 U+1793 U+1794 U+1795 U+1796 U+1797 \ + U+1798 U+1799 U+179A U+179B U+179C U+179D U+179E U+179F \ + U+17A0 U+17A1 U+17A2 + +c Arabic digits 0 through 9 + +store(arabic_digit_key) [U_0030] [U_0031] [U_0032] [U_0033] [U_0034] [U_0035] [U_0036] [U_0037] [U_0038] [U_0039] +store(arabic_digit_out) U+0030 U+0031 U+0032 U+0033 U+0034 U+0035 U+0036 U+0037 U+0038 U+0039 + +c These stores are for consonant (cluster) and consonant shifter configuration + +store(v_above) U+17B7 U+17B8 U+17B9 U+17BA \ + U+17BE U+17D0 c These vowels are rendered above the base consonant. +store(shiftable_c_1st) U+179F U+17A0 U+17A2 c These consonants shift to the 2nd series when adding U+17CA to them. +store(shiftable_BA) U+1794 c BA is unique because it can be used with both U+17CA (shift to 2nd) and and U+179C (shift to PA, still in 1st series). +store(shiftable_c_2nd) U+1784 U+1789 U+1798 U+1799 \ + U+179A U+179C U+1793 U+179B c These consonants shift to the 1st series when adding U+17C9 to them. +store(shiftable_c_2nd_with_BA) outs(shiftable_c_2nd) outs(shiftable_BA) +store(c_2nd_combo_LO) U+1799 U+1798 U+1784 U+1794 \ + U+179C c These can be combined with U+179B to make a 2nd series consonant cluster. +store(c_2nd_combo_MO) U+1799 U+179B U+1784 U+179A c These can be combined with U+1798 to make a 2nd series consonant cluster. +store(c_1st_combo_LO) U+1794 U+17A0 U+17A2 c they can be combined with U+179B to make a 1st series consonant cluster. +store(c_1st_combo_MO) U+17A0 U+179F U+17A2 c they can be combined with U+1798 to make a 1st series consonant cluster. +store(c_combo_SA) U+1794 U+1799 U+179B U+1798 \ + U+1793 U+1789 U+1784 U+179A \ + U+179C U+17A2 c they can be combined with U+179F to make a 1st series consonant cluster. +store(c_combo_QA) U+1786 U+1788 U+1794 U+1795 \ + U+178F U+1791 c they can be combined with U+17A2 to make a 1st series consonant cluster. +store(c_combo_HA) U+179C U+17A3 c they can be combined with U+17A0 to make a 1st series consonant cluster. + + + +c ==============================================BASIC RULES & CONSTRAINTS/ROTATIONS============================================== + + + +group(main) using keys + +c Basic rules with RALT key + + + any(c_key) > index(c_out,1) + + any(v_key) > index(v_out,1) + + any(ind_v_key) > index(ind_v_out,1) + + any(diacritic_key) > index(diacritic_out,1) + + any(c_shifter_key) > index(c_shifter,1) + + any(punct_key) > index(punct_out,1) + + any(latin_punct_key) > index(latin_punct_out,1) + + any(currency_key) > index(currency_out,1) + + any(digit_key) > index(digit_out,1) + + any(lek_attak_key) > index(lek_attak_out,1) + + any(lunar_date_key) > index(lunar_date_out,1) + + any(spaces_key) > index(spaces_out,1) + +c two-unicode-code-point vowels + ++ [SHIFT K_A] > U+17B6 U+17C6 ++ [SHIFT K_V] > U+17C1 U+17C7 ++ [SHIFT K_COLON] > U+17C4 U+17C7 ++ [K_COMMA] > U+17BB U+17C6 ++ [SHIFT K_COMMA] > U+17BB U+17C7 + +c Constraints + +any(v_gen) + [K_QUOTE] > context beep c no Bantoc after any genuine vowel +any(v_pseudo) + [K_QUOTE] > context beep c no Bantoc after any pseudo vowel +any(c_shifter) + [K_QUOTE] > context beep c no Bantoc after Triisap/Muusikatoan +U+17D2 any(c_out) + [K_QUOTE] > context beep c no Bantoc on a subscript +any(c_shifter) + any(c_shifter_key) > context beep c no two Triisap/Muusikatoan + +c Rotation + +U+17C7 + [SHIFT K_H] > U+17C8 c U+17C7 is changed to U+17C8 when pressing [SHIFT K_H]. +U+17C8 + [SHIFT K_H] > U+17C7 c U+17C8 is changed to U+17C7 when K_H is pressed. +c any(v_gen) U+17C7 + [SHIFT K_H] > context beep c Don't rotate between U+17C7 and U+17C8. + +c Press Space twice to get a White Space + +U+200B + [K_SPACE] > U+0020 + +c One backspace deletes a two-part vowels (i.e. psuedoVowel + U+17C7) + +any(v_combo_N) U+17C6 + [K_BKSP] > nul +any(v_combo_R) U+17C7 + [K_BKSP] > nul + +c When pressing [K_U] after ាំ and ុំ, the U+17BB is converted to a respective consonant shifter placing before them (ាំ and ុំ). + +any(shiftable_c_1st) U+17B6 U+17C6 + [K_U] > context(1) U+17CA context(2) context(3) c fix ស ាំ ុ > ស៊ាំ +any(shiftable_c_2nd_with_BA) U+17B6 U+17C6 + [K_U] > context(1) U+17C9 context(2) context(3) c fix ម ាំ ុ > ម៉ាំ + +c Consonant shifter constraints (beep when the consonant shifter is used in inappropriate environment) + +any(shiftable_c_1st) + [SHIFT K_QUOTE] > context U+17C9 beep +any(shiftable_c_2nd_with_BA) + [K_SLASH] > context U+17CA beep + +c 1st series consonant clusters + +any(c_combo_QA) U+17D2 U+17A2 + [SHIFT K_QUOTE] > context U+17C9 beep +U+179B U+17D2 any(c_1st_combo_LO) + [SHIFT K_QUOTE] > context U+17C9 beep +U+1798 U+17D2 any(c_1st_combo_MO) + [SHIFT K_QUOTE] > context U+17C9 beep +U+179F U+17D2 any(c_combo_SA) + [SHIFT K_QUOTE] > context U+17C9 beep +any(c_combo_HA) U+17D2 U+17A0 + [SHIFT K_QUOTE] > context U+17C9 beep +U+17A2 U+17D2 U+1784 + [SHIFT K_QUOTE] > context U+17C9 beep +U+17A2 U+17D2 U+179C + [SHIFT K_QUOTE] > context U+17C9 beep + +c 2nd series consonant clusters + +U+179B U+17D2 any(c_2nd_combo_LO) + [K_SLASH] > context U+17CA beep +U+1798 U+17D2 any(c_2nd_combo_MO) + [K_SLASH] > context U+17CA beep + + +c ==============================================MOBILE/TABLET TOUCH LAYOUT============================================== + + + ++ any(input_subcons) > U+17D2 index(subcons, 1) + +c delete a subscript (a subscript sign + a consonant) upon a backspace + +platform('touch') U+17D2 any(c_out) + [K_BKSP] > nul c working OK + + + [K_NPSTAR] > U+002A + + [SHIFT K_NPSTAR] > U+002A + + [K_NPPLUS] > U+002B + + [SHIFT K_NPPLUS] > U+002B + + [K_NPMINUS] > U+002D + + [SHIFT K_NPMINUS] > U+002D + + [K_NPDOT] > U+002E + + [SHIFT K_NPDOT] > U+002E + + [K_NPSLASH] > U+002F + + [SHIFT K_NPSLASH] > U+002F + + + +c ==============================================NORMALIZATION============================================== + + + +match > use(normalise) + +group(normalise) + +c Illegitimate vowel combinations should be transformed to the legitimate ones (Case #6 and #7). + +U+17C1 U+17B6 > U+17C4 c when type េ and ា, tranform them to ោ +U+17B6 U+17C1 > U+17C4 c when type ា and េ, tranform them to ោ +U+17C1 U+17B8 > U+17BE c when type េ and ី , tranform them to ើ +U+17B8 U+17C1 > U+17BE c when type ី and េ, tranform them to ើ + +c Illegitimate vowel combinations should be transformed to the legitimate ones (Case #5). + +U+17C6 U+17BB > U+17BB U+17C6 c when type ំ and ុ , tranform them to ុំ +U+17C6 U+17B6 > U+17B6 U+17C6 c when type ំ and ា, tranform them to ាំ + + +c Rotate vowels + +any(v_gen) any(v_gen) > context(2) c the last vowel stroke replaces the previous one. + +c Rotation of two-part vowels + +any(v_gen) any(v_pseudo) any(v_gen) > context(3) c a two-part vowel rotates to a genuine vowel. +any(v_gen) any(v_pseudo) any(v_pseudo) > context(3) c a two-part vowel rotates to a pseudo vowel. +any(v_gen) any(v_gen) any(v_pseudo) > context(2) context(3) c a genuine vowel rotates to a two-part vowel. +any(v_pseudo) any(v_gen) any(v_pseudo) > context(2) context(3) c a pseudo vowel roates to a two-part vowel. +any(v_gen) any(v_pseudo) any(v_gen) any(v_pseudo) > context(3) context(4) c a two-part vowel rotates to another two-part vowel. +any(v_pseudo) any(v_pseudo) > context(2) c a pseudo vowel rotates to another pseudo vowel. +c any(v_pseudo) any(v_gen) > context(2) c a pseudo vowel roates to a genuine vowel. + +c Delete extraneous coeng markers + +U+17D2 U+17D2 > context(1) c no two coengs are allowed. +U+17D2 any(v_any) > context(2) c The coeng is deleted when a vowel is put after it. +U+17D2 any(v_gen) any(v_pseudo) > context(2) context(3) c The coeng is deleted when a pseudo vowel is put after it. + +c Case #1: when a subscript is typed after a vowel, the subscript should be automatically moved to before the vowel. + +any(v_any) U+17D2 any(subcons) > context(2) context(3) context(1) c a vowel has to come after a subscript. +any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c these two-part vowels have to come after a subscript. +any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c these two-part vowels have to come after a subscript. + +c a subscript placed after a shifter and a vowel (a genuine/pseudo vowel or a two-part vowel) has to move to before them. + +any(c_shifter) any(v_any) U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c after a genuine/pseudo vowel +any(c_shifter) any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c after a two-part vowel +any(c_shifter) any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c after a two-part vowel + +c Case #2: when [D2+9A] is typed before a subscript, it should be moved to after it. + +U+17D2 U+178A U+17D2 U+179A > context(1) U+178F context(3) context(4) c change subscript ្ដ to ្ត after ្រ +U+17D2 U+179A U+17D2 U+178A > context(3) U+178F context(1) context(2) c ្ដ placed after ្រ has to be changed to ្ត and placed before ្រ + +U+17D2 U+178A any(v_any) U+17D2 U+179A > context(1) U+178F context(4) context(5) context(3) c fixed 6e +U+17D2 U+178A any(v_combo_N) U+17C6 U+17D2 U+179A > context(1) U+178F context(5) context(6) context(3) context(4) +U+17D2 U+178A any(v_combo_R) U+17C7 U+17D2 U+179A > context(1) U+178F context(5) context(6) context(3) context(4) + +U+17D2 U+179A any(v_any) U+17D2 U+178A > context(1) U+178F context(4) context(2) context(3) c fixed 6g +U+17D2 U+179A any(v_combo_N) U+17C6 U+17D2 U+178A > context(1) U+178F context(5) context(2) context(3) context(4) +U+17D2 U+179A any(v_combo_R) U+17C7 U+17D2 U+178A > context(1) U+178F context(5) context(2) context(3) context(4) + +U+17D2 U+179A any(c_shifter) U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c when a shifter intervenes, move it to the end. +U+17D2 U+179A U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c move any subscript placed after [D2+DA] to before them +U+17D2 U+179A any(v_any) U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c when a vowel intervenes, move it to the end +U+17D2 U+179A any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(5) context(6) context(1) context(2) context(3) context(4) c when a two-part vowel intervenes, move it to the end too. +U+17D2 U+179A any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(5) context(6) context(1) context(2) context(3) context(4) c (same as stated right above) + +c when a subscript is placed after a consonant shifter, they should be reversed. (Case #3) + +any(c_shifter) U+17D2 any(subcons) > context(2) context(3) context(1) + +c Reordering - Triisap and Muusikatoan store before a vowel and a vowelcomboR and pseudo vowel. + +any(v_any) any(c_shifter) > context(2) context(1) c place either a enguine or pseudo vowel after a consonant shifter +any(v_gen) any(v_pseudo) any(c_shifter) > context(3) context(1) context(2) c place a two-part vowel after a consonant shifter(i.e. មោះ៉ ) +any(c_shifter) any(v_any) any(c_shifter) > context(3) context(2) c when either a genuine or pseudo vowel is in between two consonant shifters, replace the one on the left with the one on the right. +any(c_shifter) any(v_gen) any(v_pseudo) any(c_shifter) > context(2) context(3) context(4) c when a two-part vowel is in between two consonants shifter, replace the one on the left with the one the right. + +c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +c + when [BB] is place either before or after an above vowel (ិ ី ឹ ឺ ើ and ាំ) and a Samyok Sannya (Case #4) + +c + the [BB] should transform to a respective consonant shifter placed before the vowel. + +c + the consonant shifter is a Trisap if the base consonant is ស ហ អ + +c + the consonant shifter is a Muusikatoan if the base consonant is ង ញ ម យ រ វ ប and ន ល + +c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +c consonant clusters --1st > 2nd series (with U+17BB) + +any(c_combo_QA) U+17D2 U+17A2 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +any(c_combo_QA) U+17D2 U+17A2 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+179B U+17D2 any(c_1st_combo_LO) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+179B U+17D2 any(c_1st_combo_LO) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+1798 U+17D2 any(c_1st_combo_MO) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+179F U+17D2 U+1794 U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c prevent ស្បុិ from transforming to ស្ប៊ិ​​​, but produce ស្ប៉ិ instead because ប never have Triisap shifted down. +U+179F U+17D2 U+1794 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) +U+179F U+17D2 U+1794 any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) +U+179F U+17D2 U+1794 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) + +U+179F U+17D2 any(c_combo_SA) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+179F U+17D2 any(c_combo_SA) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+179F U+17D2 any(c_combo_SA) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+179F U+17D2 any(c_combo_SA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +any(c_combo_HA) U+17D2 U+17A0 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +any(c_combo_HA) U+17D2 U+17A0 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+17A2 U+17D2 U+1784 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+17A2 U+17D2 U+1784 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+17A2 U+17D2 U+1784 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+17A2 U+17D2 U+1784 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+17A2 U+17D2 U+179C U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+17A2 U+17D2 U+179C U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+17A2 U+17D2 U+179C any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+17A2 U+17D2 U+179C U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+17A0 U+17D2 U+1794 U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c change U+17BB preceded by HA and coeng BA to U+17C9 before the above vowel +U+17A0 U+17D2 U+1794 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(3) context(6) c change U+17BB preceded by HA and coeng BA to U+17C9 before ាំ +U+17A0 U+17D2 U+1794 any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) c change U+17BB preceded by HA and coeng BA to U+17C9 before the above vowel +U+17A0 U+17D2 U+1794 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) c change U+17BB preceded by HA and coeng BA to U+17C9 before U+17B6 U+17C6 + + +U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before the above vowel +U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(3) context(6) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before ាំ +U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before the above vowel +U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before U+17B6 U+17C6 + +c consonant clusters --2nd > 1st series (with U+17BB) + +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) +U+179B U+17D2 any(c_2nd_combo_LO) any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) + +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) +U+1798 U+17D2 any(c_2nd_combo_MO) any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) + +c single consonants --1st > 2nd series (with U+17BB) + +any(shiftable_c_1st) U+17BB any(v_above) > context(1) U+17CA context(3) c change U+17BB to U+17CA before the above vowel +any(shiftable_c_1st) U+17BB U+17B6 U+17C6 > context(1) U+17CA context(3) context(4) c change U+17BB to U+17CA before ាំ +any(shiftable_c_1st) any(v_above) U+17BB > context(1) U+17CA context(2) c change U+17BB to U+17CA and place it before the above vowel +any(shiftable_c_1st) U+17BB U+17C6 U+17B6 U+17C6 > context(1) U+17CA context(4) context(5) c change U+17BB U+17C6 to U+17CA + +c single consonants --2nd > 1st series (with U+17BB) + +any(shiftable_c_2nd_with_BA) U+17BB any(v_above) > context(1) U+17C9 context(3) c change U+17BB to U+17C9 before the above vowel +any(shiftable_c_2nd_with_BA) U+17BB U+17B6 U+17C6 > context(1) U+17C9 context(3) context(4) c change U+17BB to U+17C9 before ាំ +any(shiftable_c_2nd_with_BA) any(v_above) U+17BB > context(1) U+17C9 context(2) c change U+17BB to U+17C9 before the above vowel +any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) U+17C9 context(4) context(5) c change U+17BB U+17C6 to U+17C9 + +c prevent incorrect use of consonant shifter with 2nd series consonant clusters (without U+17BB) + +U+179B U+17D2 any(c_2nd_combo_LO) U+17CA any(v_above) > context(1) context(2) context(3) U+17C9 context(5) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA any(v_above) > context(1) context(2) context(3) U+17C9 context(5) +U+179B U+17D2 any(c_2nd_combo_LO) U+17CA any(v_gen) any(v_pseudo) > context(1) context(2) context(3) U+17C9 context(5) context(6) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA any(v_gen) any(v_pseudo) > context(1) context(2) context(3) U+17C9 context(5) context(6) + +c single consonants --2nd > 1st series (without U+17BB) + +any(shiftable_c_2nd) U+17CA any(v_above) > context(1) U+17C9 context(3) c change U+17CA to U+17C9 before the above vowel +any(shiftable_c_2nd) U+17CA any(v_gen) any(v_pseudo) > context(1) U+17C9 context(3) context(4) c change U+17CA to U+17C9 before ាំ + +U+17D2 any(shiftable_c_2nd) U+17CA any(v_above) > context c stop ស្រ៊ី from becoming ស្រ៉ី +U+17D2 any(shiftable_c_2nd) U+17CA any(v_gen) any(v_pseudo) > context + +c SPECIAL CASE -- the following clusters may accept either of the two consonant shifters. + +U+1794 U+17D2 U+1799 any(c_shifter) > context +U+179F U+17D2 U+1794 any(c_shifter) > context +U+1786 U+17D2 U+1794 any(c_shifter) > context +U+1794 U+17D2 U+1799 any(c_shifter) > context +U+179F U+17D2 U+1794 any(c_shifter) > context +U+1786 U+17D2 U+1794 any(c_shifter) > context + +c single --1st > 2nd series (without U+17BB) + +any(shiftable_c_1st) U+17C9 any(v_above) > context(1) U+17CA context(3) c change U+17C9 to U+17CA before the above vowel +any(shiftable_c_1st) U+17C9 any(v_gen) any(v_pseudo) > context(1) U+17CA context(3) context(4) c change U+17C9 to U+17CA before ាំ + +c consonant clusters --1st > 2nd series (without U+17BB) + +any(c_combo_QA) U+17D2 U+17A2 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+179B U+17D2 any(c_1st_combo_LO) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+179F U+17D2 U+1794 U+17C9 any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c prevent ស្ប៉ី from transforming to ស្ប៊ី +U+179F U+17D2 any(c_combo_SA) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +any(c_combo_HA) U+17D2 U+17A0 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+17A2 U+17D2 U+1784 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+17A2 U+17D2 U+179C U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +any(c_combo_QA) U+17D2 U+17A2 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+179B U+17D2 any(c_1st_combo_LO) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+179F U+17D2 U+1794 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) c prevent ស្ប៉ាំ from transforming to ស្ប៊ាំ +U+179F U+17D2 any(c_combo_SA) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +any(c_combo_HA) U+17D2 U+17A0 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+17A2 U+17D2 U+1784 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+17A2 U+17D2 U+179C U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) + +c U+17B6 U+17BB and U+17C6 in that order preceeded by any first series consonant (cluster) are transformed to U+17CA U+17B6 and U+17C6 + +any(shiftable_c_1st) U+17B6 U+17BB U+17C6 > context(1) U+17CA context(2) context(4) +any(shiftable_c_1st) U+17BB U+17C6 U+17B6 > context(1) U+17CA context(4) context(3) + +any(c_combo_QA) U+17D2 U+17A2 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+179B U+17D2 any(c_1st_combo_LO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+1798 U+17D2 any(c_1st_combo_MO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+179F U+17D2 any(c_combo_SA) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+179F U+17D2 any(c_combo_SA) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +any(c_combo_HA) U+17D2 U+17A0 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+17A2 U+17D2 U+1784 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+17A2 U+17D2 U+1784 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+17A2 U+17D2 U+179C U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+17A2 U+17D2 U+179C U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(4) context(6) + +c U+17B6 U+17BB and U+17C6 in that order preceeded by any second series consonant (cluster) are transformed to U+17C9 U+17B6 and U+17C6 + +any(shiftable_c_2nd_with_BA) U+17B6 U+17BB U+17C6 > context(1) U+17C9 context(2) context(4) +any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 > context(1) U+17C9 context(4) context(3) + +U+179B U+17D2 any(c_2nd_combo_LO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17C9 context(4) context(6) +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17C9 context(6) context(5) + +U+1798 U+17D2 any(c_2nd_combo_MO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17C9 context(4) context(6) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17C9 context(6) context(5) + +c េុី/ុេី/៉េី > ៊ើ + +any(shiftable_c_1st) U+17C1 U+17BB U+17B8 > context(1) U+17CA U+17BE c cancel out by vowel rotation +any(shiftable_c_1st) U+17BB U+17C1 U+17B8 > context(1) U+17CA U+17BE c cancel out by vowel rotation +any(shiftable_c_1st) U+17C9 U+17C1 U+17B8 > context(1) U+17CA U+17BE + +any(c_combo_QA) U+17D2 U+17A2 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +any(c_combo_QA) U+17D2 U+17A2 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+179B U+17D2 any(c_1st_combo_LO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+179B U+17D2 any(c_1st_combo_LO) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+1798 U+17D2 any(c_1st_combo_MO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+179F U+17D2 any(c_combo_SA) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+179F U+17D2 any(c_combo_SA) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+179F U+17D2 any(c_combo_SA) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +any(c_combo_HA) U+17D2 U+17A0 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +any(c_combo_HA) U+17D2 U+17A0 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+17A2 U+17D2 U+1784 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+17A2 U+17D2 U+1784 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+17A2 U+17D2 U+1784 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+17A2 U+17D2 U+179C U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+17A2 U+17D2 U+179C U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+17A2 U+17D2 U+179C U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +c េុី/ុេី/៊េី > ៉ើ + +any(shiftable_c_2nd) U+17C1 U+17BB U+17B8 > context(1) U+17C9 U+17BE c cancel out by vowel rotation +any(shiftable_c_2nd) U+17BB U+17C1 U+17B8 > context(1) U+17C9 U+17BE c cancel out by vowel rotation +any(shiftable_c_2nd) U+17CA U+17C1 U+17B8 > context(1) U+17C9 U+17BE + +U+179B U+17D2 any(c_2nd_combo_LO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation +U+179B U+17D2 any(c_2nd_combo_LO) U+17CA U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE + +U+1798 U+17D2 any(c_2nd_combo_MO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation +U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE + +c place Robat after a base before any vowel + +any(c_out) any(v_gen) U+17CC > context(1) context(3) context(2) + +c Use appropriate identical subscript in two separate contexts. (Case #8) + +U+178E U+17D2 U+178F > context(1) context(2) U+178A +U+1793 U+17D2 U+178A > context(1) context(2) U+178F + +c BONUSES​(note that these combinations are invalid and they can't be found in the CN dictionary 1967 nor RH 1997, but users think they are) + +U+1791 U+17D2 U+1794 > U+17A1 c ទ្ប transforms to ឡ + +U+1794 U+17D2 U+1789 > U+17AB c ប្ញ transforms to ឫ +U+17AB U+17BB > U+17AC c ឫុ transforms to ឬ + +U+17AD U+17B6 > U+1789 c ឭា transforms to ញ +U+17AE U+17B6 > U+1789 c ឮា transforms to ញ + +U+1796 U+17D2 U+1789 > U+17AD c ព្ញ transforms to ឭ +U+17AD U+17BB > U+17AE c ឭុ transforms to ឮ + +U+1796 U+17D2 U+178B > U+17B0 c ព្ឋ transforms to ឰ + +U+17A7 U+17B7 > U+17B1 c ឧិ transforms to ឱ +U+17A7 U+17CC > U+17B1 c ឧ៌ transforms to ឱ +U+17A7 U+17CD > U+17B1 c ឧ៍ transforms to ឱ + +c U+17D4 U+179B U+17D4 > U+17D8 c ។ល។ transforms to U+17D8 + +U+178A U+17D2 U+1792 > U+178A U+17D2 U+178B c ដ្ធ > ដ្ឋ +U+1791 U+17D2 U+178B > U+1791 U+17D2 U+1792 c ទ្ឋ > ទ្ធ + +c additional rules + +U+1796 U+1793 U+17D2 U+178B > context(1) context(2) context(3) U+1792 c ពន្ឋ > ពន្ធ +U+1796 U+17D0 U+1793 U+17D2 U+178B > context(1) context(2) context(3) context(4) U+1792 c ព័ន្ឋ > ព័ន្ធ + + +U+17AA U+17D2 U+1799 > U+17B1 context(2) context(3) c ឪ្យ > ឱ្យ +U+17B3 U+17D2 U+1799 > U+17B1 context(2) context(3) c ឳ្យ > ឱ្យ + +U+1789 U+17D2 U+179C > U+1796 context(2) context(3) U+17B6 c ញ្វ (as in សញ្វវុធ) > ព្វា as in សញ្វវុធ + +U+17D2 U+1799 U+17C1 U+17BA > U+17BF c េ្យឺ > ឿ +U+17D2 U+1799 U+17C1 U+17B9 > U+17BF c េ្យឹ > ឿ +U+17D2 U+1799 U+17C1 U+17B8 > U+17BF c េ្យី > ឿ + +U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17BA > context(3) context(4) U+17BF c គ្រេ្យឺង > គ្រឿង +U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17B9 > context(3) context(4) U+17BF c គ្រេ្យឹង > គ្រឿង +U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17B8 > context(3) context(4) U+17BF c គ្រេ្យីង > គ្រឿង + +c In everyday practice, both 'regular space' and 'no-break space' are not used before punctuation, i.e. ។ ! etc. +c An annoying issue of having line broken up at the end of the line dropping the punctuation (។, ! and also ៗ) to the next line. +c It may be helpful to include a rule where a 'Zero Width No-Break Space' should be output together with those punct. +c ref: https://www.unicode.org/L2/L2020/20008-core-text.pdf +c ref: https://www.unicode.org/versions/Unicode12.0.0/ch23.pdf#G12985 +c ref: https://www.compart.com/en/unicode/U+FEFF + +c EOF diff --git a/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kps b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kps new file mode 100644 index 00000000000..a6b5ca26ce8 --- /dev/null +++ b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kps @@ -0,0 +1,57 @@ + + + + 15.0.266.0 + 7.0 + + + + readme.htm + splash.gif + + + + + + + + + + Khmer Angkor + © 2015-2022 SIL International + Makara Sok + + https://keyman.com/keyboards/khmer_angkor + + + + ..\build\khmer_angkor.js + File khmer_angkor.js + 0 + .js + + + ..\build\khmer_angkor.kvk + File khmer_angkor.kvk + 0 + .kvk + + + ..\build\khmer_angkor.kmx + Keyboard Khmer Angkor + 0 + .kmx + + + + + Khmer Angkor + khmer_angkor + 1.3 + + Central Khmer (Khmer, Cambodia) + + + + + diff --git a/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kvks b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kvks new file mode 100644 index 00000000000..e9c38a464dd --- /dev/null +++ b/developer/src/kmc/test/fixtures/folder-project-no-kpj/khmer_angkor/source/khmer_angkor.kvks @@ -0,0 +1,206 @@ + + +
+ 10.0 + khmer_angkor + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + ] + [ + / + . + + + + & + + * + @ + \ + } + { + - + ÷ + : + , + + ; + < + # + > + × + $ + +   + + + + + + + + + + + + + ᧿ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + « + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + " + + % + + ( + ) + + = + + + ? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + + » + + + +
diff --git a/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/khmer_angkor.kpj b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/khmer_angkor.kpj new file mode 100644 index 00000000000..2b5513fc525 --- /dev/null +++ b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/khmer_angkor.kpj @@ -0,0 +1,12 @@ + + + + $PROJECTPATH\build + $PROJECTPATH\source + True + True + False + keyboard + 2.0 + + diff --git a/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.ico b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.ico new file mode 100644 index 00000000000..42c90c398da Binary files /dev/null and b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.ico differ diff --git a/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.keyman-touch-layout b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.keyman-touch-layout new file mode 100644 index 00000000000..711f8490083 --- /dev/null +++ b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.keyman-touch-layout @@ -0,0 +1,1950 @@ +{ + "tablet": { + "displayUnderlying": false, + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "១" + }, + { + "id": "K_2", + "text": "២" + }, + { + "id": "K_3", + "text": "៣" + }, + { + "id": "K_4", + "text": "៤" + }, + { + "id": "K_5", + "text": "៥" + }, + { + "id": "K_6", + "text": "៦" + }, + { + "id": "K_7", + "text": "៧" + }, + { + "id": "K_8", + "text": "៨" + }, + { + "id": "K_9", + "text": "៩" + }, + { + "id": "K_0", + "text": "០" + }, + { + "id": "K_HYPHEN", + "text": "ឥ" + }, + { + "id": "K_EQUAL", + "text": "ឲ" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "រ" + }, + { + "id": "K_T", + "text": "ត" + }, + { + "id": "K_Y", + "text": "យ" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ផ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឪ" + }, + { + "id": "T_new_138", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "«" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "ស" + }, + { + "id": "K_D", + "text": "ដ" + }, + { + "id": "K_F", + "text": "ថ" + }, + { + "id": "K_G", + "text": "ង" + }, + { + "id": "K_H", + "text": "ហ" + }, + { + "id": "K_J", + "text": "" + }, + { + "id": "K_K", + "text": "ក" + }, + { + "id": "K_L", + "text": "ល" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឮ" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "ឋ" + }, + { + "id": "K_X", + "text": "ខ" + }, + { + "id": "K_C", + "text": "ច" + }, + { + "id": "K_V", + "text": "វ" + }, + { + "id": "K_B", + "text": "ប" + }, + { + "id": "K_N", + "text": "ន" + }, + { + "id": "K_M", + "text": "ម" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "។" + }, + { + "id": "K_SLASH", + "text": "" + }, + { + "id": "T_new_164", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "rightalt", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "‌" + }, + { + "id": "K_2", + "text": "@" + }, + { + "id": "K_3", + "text": "" + }, + { + "id": "K_4", + "text": "$" + }, + { + "id": "K_5", + "text": "€" + }, + { + "id": "K_6", + "text": "៙" + }, + { + "id": "K_7", + "text": "៚" + }, + { + "id": "K_8", + "text": "*" + }, + { + "id": "K_9", + "text": "{" + }, + { + "id": "K_0", + "text": "}" + }, + { + "id": "K_HYPHEN", + "text": "≈" + }, + { + "id": "K_EQUAL", + "text": "" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ៜ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "ឯ" + }, + { + "id": "K_R", + "text": "ឫ" + }, + { + "id": "K_T", + "text": "ឨ" + }, + { + "id": "K_Y", + "text": "[" + }, + { + "id": "K_U", + "text": "]" + }, + { + "id": "K_I", + "text": "ឦ" + }, + { + "id": "K_O", + "text": "ឱ" + }, + { + "id": "K_P", + "text": "ឰ" + }, + { + "id": "K_LBRKT", + "text": "ឩ" + }, + { + "id": "K_RBRKT", + "text": "ឳ" + }, + { + "id": "T_new_307", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "‍" + }, + { + "id": "K_A", + "text": "+" + }, + { + "id": "K_S", + "text": "-" + }, + { + "id": "K_D", + "text": "×" + }, + { + "id": "K_F", + "text": "÷" + }, + { + "id": "K_G", + "text": ":" + }, + { + "id": "K_H", + "text": "‘" + }, + { + "id": "K_J", + "text": "’" + }, + { + "id": "K_K", + "text": "ឝ" + }, + { + "id": "K_L", + "text": "៘" + }, + { + "id": "K_COLON", + "text": "៖" + }, + { + "id": "K_QUOTE", + "text": "ៈ" + }, + { + "id": "K_BKSLASH", + "text": "\\" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "1", + "nextlayer": "shift" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "<" + }, + { + "id": "K_X", + "text": ">" + }, + { + "id": "K_C", + "text": "#" + }, + { + "id": "K_V", + "text": "&" + }, + { + "id": "K_B", + "text": "ឞ" + }, + { + "id": "K_N", + "text": ";" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "," + }, + { + "id": "K_PERIOD", + "text": "." + }, + { + "id": "K_SLASH", + "text": "/" + }, + { + "id": "T_new_333", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": " ", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + }, + { + "id": "shift", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "!" + }, + { + "id": "K_2", + "text": "ៗ" + }, + { + "id": "K_3", + "text": "\"" + }, + { + "id": "K_4", + "text": "៛" + }, + { + "id": "K_5", + "text": "%" + }, + { + "id": "K_6", + "text": "" + }, + { + "id": "K_7", + "text": "" + }, + { + "id": "K_8", + "text": "" + }, + { + "id": "K_9", + "text": "(" + }, + { + "id": "K_0", + "text": ")" + }, + { + "id": "K_HYPHEN", + "text": "" + }, + { + "id": "K_EQUAL", + "text": "=" + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_Q", + "text": "ឈ", + "pad": "75" + }, + { + "id": "K_W", + "text": "" + }, + { + "id": "K_E", + "text": "" + }, + { + "id": "K_R", + "text": "ឬ" + }, + { + "id": "K_T", + "text": "ទ" + }, + { + "id": "K_Y", + "text": "" + }, + { + "id": "K_U", + "text": "" + }, + { + "id": "K_I", + "text": "" + }, + { + "id": "K_O", + "text": "" + }, + { + "id": "K_P", + "text": "ភ" + }, + { + "id": "K_LBRKT", + "text": "" + }, + { + "id": "K_RBRKT", + "text": "ឧ" + }, + { + "id": "T_new_364", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_BKQUOTE", + "text": "»" + }, + { + "id": "K_A", + "text": "" + }, + { + "id": "K_S", + "text": "" + }, + { + "id": "K_D", + "text": "ឌ" + }, + { + "id": "K_F", + "text": "ធ" + }, + { + "id": "K_G", + "text": "អ" + }, + { + "id": "K_H", + "text": "ះ" + }, + { + "id": "K_J", + "text": "ញ" + }, + { + "id": "K_K", + "text": "គ" + }, + { + "id": "K_L", + "text": "ឡ" + }, + { + "id": "K_COLON", + "text": "" + }, + { + "id": "K_QUOTE", + "text": "" + }, + { + "id": "K_BKSLASH", + "text": "ឭ" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_SHIFT", + "text": "*Shift*", + "width": "160", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_oE2", + "text": "" + }, + { + "id": "K_Z", + "text": "ឍ" + }, + { + "id": "K_X", + "text": "ឃ" + }, + { + "id": "K_C", + "text": "ជ" + }, + { + "id": "K_V", + "text": "" + }, + { + "id": "K_B", + "text": "ព" + }, + { + "id": "K_N", + "text": "ណ" + }, + { + "id": "K_M", + "text": "" + }, + { + "id": "K_COMMA", + "text": "" + }, + { + "id": "K_PERIOD", + "text": "៕" + }, + { + "id": "K_SLASH", + "text": "?" + }, + { + "id": "T_new_390", + "text": "", + "width": "10", + "sp": "10" + } + ] + }, + { + "id": 5, + "key": [ + { + "id": "K_LCONTROL", + "text": "*AltGr*", + "width": "160", + "sp": "1", + "nextlayer": "rightalt" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "160", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "", + "width": "930" + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "160", + "sp": "1" + } + ] + } + ] + } + ], + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + }, + "phone": { + "layer": [ + { + "id": "default", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_Q", + "text": "ឆ", + "pad": "", + "sk": [ + { + "text": "ឈ", + "id": "K_Q", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1786" + }, + { + "text": "", + "id": "T_17D2_1788" + } + ] + }, + { + "id": "K_W", + "text": "", + "sk": [ + { + "text": "", + "id": "K_W", + "layer": "shift" + } + ] + }, + { + "id": "K_E", + "text": "", + "sk": [ + { + "text": "", + "id": "K_E", + "layer": "shift" + }, + { + "text": "", + "id": "K_S", + "layer": "shift" + }, + { + "text": "", + "id": "K_V", + "layer": "shift" + }, + { + "text": "ឯ", + "id": "U_17AF" + }, + { + "text": "ឰ", + "id": "U_17B0" + } + ] + }, + { + "id": "K_R", + "text": "រ", + "sk": [ + { + "text": "", + "id": "T_17D2_179A" + }, + { + "text": "ឫ", + "id": "U_17AB" + }, + { + "text": "ឬ", + "id": "U_17AC" + } + ] + }, + { + "id": "K_T", + "text": "ត", + "sk": [ + { + "text": "ទ", + "id": "K_T", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178F" + }, + { + "text": "", + "id": "T_17D2_1791", + "layer": "default" + } + ] + }, + { + "id": "K_Y", + "text": "យ", + "sk": [ + { + "text": "", + "id": "T_17D2_1799" + } + ] + }, + { + "id": "K_U", + "text": "", + "sk": [ + { + "text": "", + "id": "K_U", + "layer": "shift" + }, + { + "text": "", + "id": "K_Y", + "layer": "shift" + }, + { + "text": "ឧ", + "id": "U_17A7" + }, + { + "text": "ឪ", + "id": "U_17AA", + "layer": "shift" + }, + { + "text": "ឩ", + "id": "U_17A9", + "layer": "shift" + }, + { + "text": "ឨ", + "id": "U_17A8" + } + ] + }, + { + "id": "K_I", + "text": "", + "sk": [ + { + "text": "", + "id": "K_I", + "layer": "shift" + }, + { + "text": "ឥ", + "id": "U_17A5" + }, + { + "text": "ឦ", + "id": "U_17A6", + "layer": "shift" + } + ] + }, + { + "id": "K_O", + "text": "", + "sk": [ + { + "text": "", + "id": "K_O", + "layer": "shift" + }, + { + "text": "", + "id": "K_LBRKT" + }, + { + "text": "", + "id": "K_LBRKT", + "layer": "shift" + }, + { + "text": "", + "id": "K_COLON", + "layer": "shift" + }, + { + "text": "ឱ", + "id": "U_17B1" + }, + { + "text": "ឲ", + "id": "U_17B2" + }, + { + "text": "ឳ", + "id": "U_17B3", + "layer": "shift" + } + ] + }, + { + "id": "K_P", + "text": "ផ", + "sk": [ + { + "text": "ភ", + "id": "K_P", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1795" + }, + { + "text": "", + "id": "T_17D2_1797", + "layer": "default" + } + ] + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "K_A", + "text": "", + "pad": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_A", + "layer": "shift" + } + ] + }, + { + "id": "K_S", + "text": "ស", + "sk": [ + { + "text": "", + "id": "T_17D2_179F" + }, + { + "text": "ឝ", + "id": "U_179D" + }, + { + "text": "ឞ", + "id": "U_179E" + } + ] + }, + { + "id": "K_D", + "text": "ដ", + "sk": [ + { + "text": "ឌ", + "id": "K_D", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178A" + }, + { + "text": "", + "id": "T_17D2_178C", + "layer": "default" + } + ] + }, + { + "id": "K_F", + "text": "ថ", + "sk": [ + { + "text": "ធ", + "id": "K_F", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1790" + }, + { + "text": "", + "id": "T_17D2_1792", + "layer": "default" + } + ] + }, + { + "id": "K_G", + "text": "ង", + "sk": [ + { + "text": "អ", + "id": "K_G", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1784" + }, + { + "text": "", + "id": "T_17D2_17A2", + "layer": "default" + } + ] + }, + { + "id": "K_H", + "text": "ហ", + "sk": [ + { + "text": "", + "id": "T_17D2_17A0" + }, + { + "text": "ះ", + "id": "K_H", + "layer": "shift" + }, + { + "text": "ៈ", + "id": "U_17C8" + } + ] + }, + { + "id": "K_J", + "text": "ញ", + "layer": "shift", + "sk": [ + { + "text": "", + "id": "T_17D2_1789" + } + ] + }, + { + "id": "K_K", + "text": "ក", + "sk": [ + { + "text": "គ", + "id": "K_K", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1780" + }, + { + "text": "", + "id": "T_17D2_1782" + } + ] + }, + { + "id": "K_L", + "text": "ល", + "sk": [ + { + "text": "ឡ", + "id": "K_L", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_179B" + }, + { + "text": "ឭ", + "id": "U_17AD" + }, + { + "text": "ឮ", + "id": "U_17AE" + } + ] + }, + { + "id": "K_COLON", + "text": "" + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "K_Z", + "text": "ឋ", + "pad": "", + "width": "", + "sk": [ + { + "text": "ឍ", + "id": "K_Z", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_178B" + }, + { + "text": "", + "id": "T_17D2_178D", + "layer": "default" + } + ] + }, + { + "id": "K_X", + "text": "ខ", + "sk": [ + { + "text": "ឃ", + "id": "K_X", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1781" + }, + { + "text": "", + "id": "T_17D2_1783", + "layer": "default" + } + ] + }, + { + "id": "K_C", + "text": "ច", + "sk": [ + { + "text": "ជ", + "id": "K_C", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1785" + }, + { + "text": "", + "id": "T_17D2_1787", + "layer": "default" + } + ] + }, + { + "id": "K_V", + "text": "វ", + "sk": [ + { + "text": "", + "id": "T_17D2_179C" + } + ] + }, + { + "id": "K_B", + "text": "ប", + "sk": [ + { + "text": "ព", + "id": "K_B", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1794" + }, + { + "text": "", + "id": "T_17D2_1796", + "layer": "default" + } + ] + }, + { + "id": "K_N", + "text": "ន", + "sk": [ + { + "text": "ណ", + "id": "K_N", + "layer": "shift" + }, + { + "text": "", + "id": "T_17D2_1793" + }, + { + "text": "", + "id": "T_17D2_178E", + "layer": "default" + } + ] + }, + { + "id": "K_M", + "text": "ម", + "sk": [ + { + "text": "", + "id": "T_17D2_1798" + }, + { + "text": "", + "id": "K_M", + "layer": "shift" + } + ] + }, + { + "id": "K_COMMA", + "text": "", + "sk": [ + { + "text": "", + "id": "K_COMMA", + "layer": "shift" + }, + { + "text": "", + "id": "K_6", + "layer": "shift" + }, + { + "text": "", + "id": "K_7", + "layer": "shift" + }, + { + "text": "", + "id": "K_8", + "layer": "shift" + }, + { + "text": "", + "id": "K_HYPHEN", + "layer": "shift" + }, + { + "text": "", + "id": "U_17D1", + "layer": "shift" + }, + { + "text": "", + "id": "U_17DD", + "layer": "shift" + }, + { + "text": "", + "id": "U_17CE", + "layer": "shift" + } + ] + }, + { + "id": "K_QUOTE", + "text": "", + "width": "100", + "sk": [ + { + "text": "", + "id": "K_QUOTE", + "layer": "shift" + }, + { + "text": "", + "id": "K_SLASH" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "100", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_NUMLOCK", + "text": "១២៣", + "width": "140", + "sp": "1", + "nextlayer": "numeric" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "sp": "0", + "sk": [ + { + "text": " ", + "id": "U_0020", + "layer": "default" + } + ] + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + }, + { + "id": "numeric", + "row": [ + { + "id": 1, + "key": [ + { + "id": "K_1", + "text": "១", + "pad": "", + "sk": [ + { + "text": "1", + "id": "U_0031" + } + ] + }, + { + "id": "K_2", + "text": "២", + "sk": [ + { + "text": "2", + "id": "U_0032" + } + ] + }, + { + "id": "K_3", + "text": "៣", + "sk": [ + { + "text": "3", + "id": "U_0033" + } + ] + }, + { + "id": "K_4", + "text": "៤", + "sk": [ + { + "text": "4", + "id": "U_0034" + } + ] + }, + { + "id": "K_5", + "text": "៥", + "sk": [ + { + "text": "5", + "id": "U_0035" + } + ] + }, + { + "id": "K_6", + "text": "៦", + "sk": [ + { + "text": "6", + "id": "U_0036" + } + ] + }, + { + "id": "K_7", + "text": "៧", + "sk": [ + { + "text": "7", + "id": "U_0037" + } + ] + }, + { + "id": "K_8", + "text": "៨", + "sk": [ + { + "text": "8", + "id": "U_0038" + } + ] + }, + { + "id": "K_9", + "text": "៩", + "sk": [ + { + "text": "9", + "id": "U_0039" + } + ] + }, + { + "id": "K_0", + "text": "០", + "sk": [ + { + "text": "0", + "id": "U_0030" + }, + { + "text": "", + "id": "U_17D3" + } + ] + } + ] + }, + { + "id": 2, + "key": [ + { + "id": "U_0040", + "text": "@", + "pad": "", + "sk": [ + { + "text": "©", + "id": "U_00A9" + }, + { + "text": "®", + "id": "U_00AE" + } + ] + }, + { + "id": "U_0023", + "text": "#", + "sk": [ + { + "text": "№", + "id": "U_2116" + }, + { + "text": "~", + "id": "U_007E" + } + ] + }, + { + "id": "U_17DB", + "text": "៛", + "sk": [ + { + "text": "$", + "id": "U_0024" + }, + { + "text": "฿", + "id": "U_0E3F" + }, + { + "text": "¢", + "id": "U_00A2" + }, + { + "text": "£", + "id": "U_00A3" + }, + { + "text": "¥", + "id": "U_00A5" + } + ] + }, + { + "id": "U_0026", + "text": "&" + }, + { + "id": "U_0025", + "text": "%", + "sk": [ + { + "text": "‰", + "id": "U_2030" + }, + { + "text": "‱", + "id": "U_2031" + } + ] + }, + { + "id": "U_002B", + "text": "+", + "sk": [ + { + "text": "-", + "id": "U_002D" + }, + { + "text": "×", + "id": "U_00D7" + }, + { + "text": "÷", + "id": "U_00F7" + }, + { + "text": "±", + "id": "U_00B1" + } + ] + }, + { + "id": "U_003D", + "text": "=", + "sk": [ + { + "text": "_", + "id": "U_005F" + }, + { + "text": "≠", + "id": "U_2260" + } + ] + }, + { + "id": "U_002A", + "text": "*", + "sk": [ + { + "text": "^", + "id": "U_005E" + } + ] + }, + { + "id": "U_003F", + "text": "?", + "sk": [ + { + "text": "¿", + "id": "U_00BF" + } + ] + }, + { + "id": "U_0021", + "text": "!", + "sk": [ + { + "text": "¡", + "id": "U_00A1" + } + ] + } + ] + }, + { + "id": 3, + "key": [ + { + "id": "U_2018", + "text": "‘", + "sk": [ + { + "text": "’", + "id": "U_2019" + } + ] + }, + { + "id": "U_201C", + "text": "“", + "sk": [ + { + "text": "”", + "id": "U_201D" + } + ] + }, + { + "id": "U_00AB", + "text": "«", + "pad": "", + "sk": [ + { + "text": "»", + "id": "U_00BB" + } + ] + }, + { + "id": "U_002F", + "text": "/", + "sk": [ + { + "text": "\\", + "id": "U_005C" + }, + { + "text": "|", + "id": "U_007C" + }, + { + "text": "¦", + "id": "U_00A6" + } + ] + }, + { + "id": "U_0028", + "text": "(", + "sk": [ + { + "text": ")", + "id": "U_0029" + }, + { + "text": "[", + "id": "U_005B" + }, + { + "text": "]", + "id": "U_005D" + }, + { + "text": "{", + "id": "U_007B" + }, + { + "text": "}", + "id": "U_007D" + } + ] + }, + { + "id": "U_17D9", + "text": "៙", + "sk": [ + { + "text": "៚", + "id": "U_17DA" + }, + { + "text": "ៜ", + "id": "U_17DC" + }, + { + "text": "§", + "id": "U_00A7" + }, + { + "text": "Ø", + "id": "U_00D8" + } + ] + }, + { + "id": "U_17D7", + "text": "ៗ" + }, + { + "id": "U_003C", + "text": "<", + "sk": [ + { + "text": "≤", + "id": "U_2264" + }, + { + "text": ">", + "id": "U_003E" + }, + { + "text": "≥", + "id": "U_2265" + } + ] + }, + { + "id": "U_17D6", + "text": "៖", + "sk": [ + { + "text": ":", + "id": "U_003A" + }, + { + "text": ";", + "id": "U_003B" + }, + { + "text": "…", + "id": "U_2026" + } + ] + }, + { + "id": "K_BKSP", + "text": "*BkSp*", + "width": "", + "sp": "1" + } + ] + }, + { + "id": 4, + "key": [ + { + "id": "K_LCONTROL", + "text": "១២៣", + "width": "140", + "sp": "2", + "nextlayer": "default" + }, + { + "id": "K_LOPT", + "text": "*Menu*", + "width": "120", + "sp": "1" + }, + { + "id": "K_SPACE", + "text": "​", + "width": "555", + "sp": "0", + "layer": "shift", + "sk": [] + }, + { + "id": "K_PERIOD", + "text": "។", + "width": "120", + "sk": [ + { + "text": "៕", + "id": "K_PERIOD", + "layer": "shift" + }, + { + "text": "!", + "id": "U_0021" + }, + { + "text": "?", + "id": "U_003F" + } + ] + }, + { + "id": "K_ENTER", + "text": "*Enter*", + "width": "140", + "sp": "1" + } + ] + } + ] + } + ], + "displayUnderlying": false, + "font": "Khmer Busra Kbd", + "fontsize": "0.8em" + } +} \ No newline at end of file diff --git a/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kmn b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kmn new file mode 100644 index 00000000000..b7919afca3e --- /dev/null +++ b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kmn @@ -0,0 +1,642 @@ +store(&VERSION) '10.0' +store(&NAME) "Khmer Angkor" +store(©RIGHT) '© 2015-2022 SIL International' +store(&MESSAGE) "More than just a Khmer Unicode keyboard." +store(&TARGETS) 'any' +store(&LAYOUTFILE) 'khmer_angkor.keyman-touch-layout' +store(&KEYBOARDVERSION) '1.3' +store(&BITMAP) 'khmer_angkor.ico' +store(&VISUALKEYBOARD) 'khmer_angkor.kvks' + +begin Unicode > use(main) + + + +c ==============================================STORES============================================== + + + +c 33 consonants and two deprecated consonants + +store(c_key) [K_K] [K_X] [SHIFT K_K] [SHIFT K_X] [K_G] \ + [K_C] [K_Q] [SHIFT K_C] [SHIFT K_Q] [SHIFT K_J] \ + [K_D] [K_Z] [SHIFT K_D] [SHIFT K_Z] [SHIFT K_N] \ + [K_T] [K_F] [SHIFT K_T] [SHIFT K_F] [K_N] \ + [K_B] [K_P] [SHIFT K_B] [SHIFT K_P] [K_M] \ + [K_Y] [K_R] [K_L] [K_V] [K_S] [K_H] [SHIFT K_L] [SHIFT K_G] \ + [RALT K_K] [RALT K_B] +store(c_out) U+1780 U+1781 U+1782 U+1783 U+1784 \ + U+1785 U+1786 U+1787 U+1788 U+1789 \ + U+178A U+178B U+178C U+178D U+178E \ + U+178F U+1790 U+1791 U+1792 U+1793 \ + U+1794 U+1795 U+1796 U+1797 U+1798 \ + U+1799 U+179A U+179B U+179C U+179F U+17A0 U+17A1 U+17A2 \ + U+179D U+179E c deprecated, but they are used in minority languages + +c All genuine dependent vowels + +store(v_gen_key) [K_A] [K_I] [SHIFT K_I] [K_W] [SHIFT K_W] [K_U] [SHIFT K_U] [SHIFT K_Y] \ + [K_COLON] [SHIFT K_LBRKT] [K_LBRKT] [K_E] [SHIFT K_E] [SHIFT K_S] [K_O] [SHIFT K_O] +store(v_gen) U+17B6 U+17B7 U+17B8 U+17B9 U+17BA U+17BB U+17BC U+17BD \ + U+17BE U+17BF U+17C0 U+17C1 U+17C2 U+17C3 U+17C4 U+17C5 + +c All pseudo dependent vowels + +store(v_pseudo_key) [SHIFT K_M] [SHIFT K_H] [RALT K_QUOTE] +store(v_pseudo) U+17C6 U+17C7 U+17C8 + +c Both genuine and pseudo vowels + +store(v_key) outs(v_gen_key) outs(v_pseudo_key) +store(v_out) outs(v_gen) outs(v_pseudo) +store(v_any) outs(v_out) + +c These seven vowels can be concatenated with Reahmuk (U+17C7) to make េះ ោះ ុះ ិះ ីះ ឹះ and ែះ. + +store(v_combo_R) U+17C1 U+17C4 U+17BB U+17B7 U+17B8 U+17B9 U+17C2 + +c these two vowels can be concatenated with Nikahit (U+17C6) to make ាំ and ុំ. + +store(v_combo_N) U+17B6 U+17BB +store(v_combo) outs(v_combo_R) outs(v_combo_N) + +c Independent vowels + +store(ind_v_key) [K_HYPHEN] [RALT K_I] [SHIFT K_RBRKT] [RALT K_T] [RALT K_LBRKT] [K_RBRKT] [RALT K_R] [SHIFT K_R] \ + [SHIFT K_BKSLASH] [K_BKSLASH] [RALT K_E] [RALT K_P] [RALT K_O] [K_EQUAL] [RALT K_RBRKT] +store(ind_v_out) U+17A5 U+17A6 U+17A7 U+17A8 U+17A9 U+17AA U+17AB U+17AC \ + U+17AD U+17AE U+17AF U+17B0 U+17B1 U+17B2 U+17B3 + +c Diacritics + +store(diacritic_key) [K_QUOTE] [SHIFT K_7] [SHIFT K_HYPHEN] [SHIFT K_8] [SHIFT K_6] [RALT K_QUOTE] \ + [RALT K_EQUAL] [RALT K_3] [RALT K_W] [RALT K_Q] [K_J] +store(diacritic_out) U+17CB U+17D0 U+17CC U+17CF U+17CD U+17C8 \ + U+17CE U+17D1 U+17DD U+17DC U+17D2 + +c Consonant shifter--Muusikatoan(U+17C9) and Triisap(U+17CA) + +store(c_shifter_key) [SHIFT K_QUOTE] [K_SLASH] +store(c_shifter) U+17C9 U+17CA + +c punctuations + +store(punct_key) [K_PERIOD] [SHIFT K_PERIOD] [RALT K_COLON] [SHIFT K_2] [RALT K_L] [RALT K_6] [RALT K_7] [RALT K_M] +store(punct_out) U+17D4 U+17D5 U+17D6 U+17D7 U+17D8 U+17D9 U+17DA U+17D3 + +store(latin_punct_key) [K_BKQUOTE] [SHIFT K_BKQUOTE] [SHIFT K_9] [SHIFT K_0] [SHIFT K_1] [SHIFT K_3] \ + [SHIFT K_5] [SHIFT K_EQUAL] [SHIFT K_SLASH] [RALT K_9] [RALT K_0] [RALT K_BKSLASH] \ + [RALT K_2] [RALT K_8] [RALT K_COMMA] [RALT K_D] [RALT K_PERIOD] [RALT K_SLASH] \ + [RALT K_Y] [RALT K_U] [RALT K_BKQUOTE] [RALT K_1] [RALT K_A] [RALT K_S] \ + [RALT K_F] [RALT K_G] [RALT K_HYPHEN] [RALT K_H] [RALT K_J] [RALT K_N] \ + [RALT K_Z] [RALT K_X] [RALT K_C] [RALT K_V] +store(latin_punct_out) U+00AB U+00BB U+0028 U+0029 U+0021 U+0022 \ + U+0025 U+003D U+003F U+007B U+007D U+005C \ + U+0040 U+002A U+002C U+00D7 U+002E U+002F \ + U+005B U+005D U+200D U+200C U+002B U+002D \ + U+00F7 U+003A U+2248 U+2018 U+2019 U+003B \ + U+003C U+003E U+0023 U+0026 +c white space, ZWSP, ZWJ +store(spaces_key) [K_SPACE] [SHIFT K_SPACE] [RALT K_SPACE] +store(spaces_out) U+200B U+0020 U+00A0 + +c Currency: Riel, Dollar and Euro + +store(currency_key) [SHIFT K_4] [RALT K_4] [RALT K_5] +store(currency_out) U+17DB U+0024 U+20AC + +c Khmer digits 0 through 9 + +store(digit_key) [K_0] [K_1] [K_2] [K_3] [K_4] [K_5] [K_6] [K_7] [K_8] [K_9] +store(digit_out) U+17E0 U+17E1 U+17E2 U+17E3 U+17E4 U+17E5 U+17E6 U+17E7 U+17E8 U+17E9 + +c Khmer Lek Attak (a.k.a. divination lore) +store(lek_attak_key) [SHIFT RALT K_0] [SHIFT RALT K_1] [SHIFT RALT K_2] [SHIFT RALT K_3] [SHIFT RALT K_4] \ + [SHIFT RALT K_5] [SHIFT RALT K_6] [SHIFT RALT K_7] [SHIFT RALT K_8] [SHIFT RALT K_9] +store(lek_attak_out) U+17F0 U+17F1 U+17F2 U+17F3 U+17F4 \ + U+17F5 U+17F6 U+17F7 U+17F8 U+17F9 + +c Lunar date format (Khmer style) + +store(lunar_date_key) [SHIFT RALT K_A] [SHIFT RALT K_B] [SHIFT RALT K_C] [SHIFT RALT K_D] [SHIFT RALT K_E] \ + [SHIFT RALT K_F] [SHIFT RALT K_G] [SHIFT RALT K_H] [SHIFT RALT K_I] [SHIFT RALT K_J] \ + [SHIFT RALT K_K] [SHIFT RALT K_L] [SHIFT RALT K_M] [SHIFT RALT K_N] [SHIFT RALT K_O] \ + [SHIFT RALT K_P] [SHIFT RALT K_Q] [SHIFT RALT K_R] [SHIFT RALT K_S] [SHIFT RALT K_T] \ + [SHIFT RALT K_U] [SHIFT RALT K_V] [SHIFT RALT K_W] [SHIFT RALT K_X] [SHIFT RALT K_Y] \ + [SHIFT RALT K_Z] [SHIFT RALT K_COLON] [SHIFT RALT K_COMMA] [SHIFT RALT K_PERIOD] [SHIFT RALT K_LBRKT] \ + [SHIFT RALT K_RBRKT] [SHIFT RALT K_QUOTE] +store(lunar_date_out) U+19EC U+19FB U+19F9 U+19EE U+19E2 \ + U+19EF U+19F0 U+19F1 U+19E7 U+19F2 \ + U+19F3 U+19F4 U+19FD U+19FC U+19E8 \ + U+19E9 U+19E0 U+19E3 U+19ED U+19E4 \ + U+19E6 U+19FA U+19E1 U+19F8 U+19E5 \ + U+19F7 U+19F5 U+19FE U+19FF U+19EA \ + U+19EB U+19F6 + +c Subscripts​ for mobile/table layouts + +store(input_subcons) \ + [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] + +store(subcons) \ + U+1780 U+1781 U+1782 U+1783 U+1784 U+1785 U+1786 U+1787 \ + U+1788 U+1789 U+178A U+178B U+178C U+178D U+178E U+178F \ + U+1790 U+1791 U+1792 U+1793 U+1794 U+1795 U+1796 U+1797 \ + U+1798 U+1799 U+179A U+179B U+179C U+179D U+179E U+179F \ + U+17A0 U+17A1 U+17A2 + +c Arabic digits 0 through 9 + +store(arabic_digit_key) [U_0030] [U_0031] [U_0032] [U_0033] [U_0034] [U_0035] [U_0036] [U_0037] [U_0038] [U_0039] +store(arabic_digit_out) U+0030 U+0031 U+0032 U+0033 U+0034 U+0035 U+0036 U+0037 U+0038 U+0039 + +c These stores are for consonant (cluster) and consonant shifter configuration + +store(v_above) U+17B7 U+17B8 U+17B9 U+17BA \ + U+17BE U+17D0 c These vowels are rendered above the base consonant. +store(shiftable_c_1st) U+179F U+17A0 U+17A2 c These consonants shift to the 2nd series when adding U+17CA to them. +store(shiftable_BA) U+1794 c BA is unique because it can be used with both U+17CA (shift to 2nd) and and U+179C (shift to PA, still in 1st series). +store(shiftable_c_2nd) U+1784 U+1789 U+1798 U+1799 \ + U+179A U+179C U+1793 U+179B c These consonants shift to the 1st series when adding U+17C9 to them. +store(shiftable_c_2nd_with_BA) outs(shiftable_c_2nd) outs(shiftable_BA) +store(c_2nd_combo_LO) U+1799 U+1798 U+1784 U+1794 \ + U+179C c These can be combined with U+179B to make a 2nd series consonant cluster. +store(c_2nd_combo_MO) U+1799 U+179B U+1784 U+179A c These can be combined with U+1798 to make a 2nd series consonant cluster. +store(c_1st_combo_LO) U+1794 U+17A0 U+17A2 c they can be combined with U+179B to make a 1st series consonant cluster. +store(c_1st_combo_MO) U+17A0 U+179F U+17A2 c they can be combined with U+1798 to make a 1st series consonant cluster. +store(c_combo_SA) U+1794 U+1799 U+179B U+1798 \ + U+1793 U+1789 U+1784 U+179A \ + U+179C U+17A2 c they can be combined with U+179F to make a 1st series consonant cluster. +store(c_combo_QA) U+1786 U+1788 U+1794 U+1795 \ + U+178F U+1791 c they can be combined with U+17A2 to make a 1st series consonant cluster. +store(c_combo_HA) U+179C U+17A3 c they can be combined with U+17A0 to make a 1st series consonant cluster. + + + +c ==============================================BASIC RULES & CONSTRAINTS/ROTATIONS============================================== + + + +group(main) using keys + +c Basic rules with RALT key + + + any(c_key) > index(c_out,1) + + any(v_key) > index(v_out,1) + + any(ind_v_key) > index(ind_v_out,1) + + any(diacritic_key) > index(diacritic_out,1) + + any(c_shifter_key) > index(c_shifter,1) + + any(punct_key) > index(punct_out,1) + + any(latin_punct_key) > index(latin_punct_out,1) + + any(currency_key) > index(currency_out,1) + + any(digit_key) > index(digit_out,1) + + any(lek_attak_key) > index(lek_attak_out,1) + + any(lunar_date_key) > index(lunar_date_out,1) + + any(spaces_key) > index(spaces_out,1) + +c two-unicode-code-point vowels + ++ [SHIFT K_A] > U+17B6 U+17C6 ++ [SHIFT K_V] > U+17C1 U+17C7 ++ [SHIFT K_COLON] > U+17C4 U+17C7 ++ [K_COMMA] > U+17BB U+17C6 ++ [SHIFT K_COMMA] > U+17BB U+17C7 + +c Constraints + +any(v_gen) + [K_QUOTE] > context beep c no Bantoc after any genuine vowel +any(v_pseudo) + [K_QUOTE] > context beep c no Bantoc after any pseudo vowel +any(c_shifter) + [K_QUOTE] > context beep c no Bantoc after Triisap/Muusikatoan +U+17D2 any(c_out) + [K_QUOTE] > context beep c no Bantoc on a subscript +any(c_shifter) + any(c_shifter_key) > context beep c no two Triisap/Muusikatoan + +c Rotation + +U+17C7 + [SHIFT K_H] > U+17C8 c U+17C7 is changed to U+17C8 when pressing [SHIFT K_H]. +U+17C8 + [SHIFT K_H] > U+17C7 c U+17C8 is changed to U+17C7 when K_H is pressed. +c any(v_gen) U+17C7 + [SHIFT K_H] > context beep c Don't rotate between U+17C7 and U+17C8. + +c Press Space twice to get a White Space + +U+200B + [K_SPACE] > U+0020 + +c One backspace deletes a two-part vowels (i.e. psuedoVowel + U+17C7) + +any(v_combo_N) U+17C6 + [K_BKSP] > nul +any(v_combo_R) U+17C7 + [K_BKSP] > nul + +c When pressing [K_U] after ាំ and ុំ, the U+17BB is converted to a respective consonant shifter placing before them (ាំ and ុំ). + +any(shiftable_c_1st) U+17B6 U+17C6 + [K_U] > context(1) U+17CA context(2) context(3) c fix ស ាំ ុ > ស៊ាំ +any(shiftable_c_2nd_with_BA) U+17B6 U+17C6 + [K_U] > context(1) U+17C9 context(2) context(3) c fix ម ាំ ុ > ម៉ាំ + +c Consonant shifter constraints (beep when the consonant shifter is used in inappropriate environment) + +any(shiftable_c_1st) + [SHIFT K_QUOTE] > context U+17C9 beep +any(shiftable_c_2nd_with_BA) + [K_SLASH] > context U+17CA beep + +c 1st series consonant clusters + +any(c_combo_QA) U+17D2 U+17A2 + [SHIFT K_QUOTE] > context U+17C9 beep +U+179B U+17D2 any(c_1st_combo_LO) + [SHIFT K_QUOTE] > context U+17C9 beep +U+1798 U+17D2 any(c_1st_combo_MO) + [SHIFT K_QUOTE] > context U+17C9 beep +U+179F U+17D2 any(c_combo_SA) + [SHIFT K_QUOTE] > context U+17C9 beep +any(c_combo_HA) U+17D2 U+17A0 + [SHIFT K_QUOTE] > context U+17C9 beep +U+17A2 U+17D2 U+1784 + [SHIFT K_QUOTE] > context U+17C9 beep +U+17A2 U+17D2 U+179C + [SHIFT K_QUOTE] > context U+17C9 beep + +c 2nd series consonant clusters + +U+179B U+17D2 any(c_2nd_combo_LO) + [K_SLASH] > context U+17CA beep +U+1798 U+17D2 any(c_2nd_combo_MO) + [K_SLASH] > context U+17CA beep + + +c ==============================================MOBILE/TABLET TOUCH LAYOUT============================================== + + + ++ any(input_subcons) > U+17D2 index(subcons, 1) + +c delete a subscript (a subscript sign + a consonant) upon a backspace + +platform('touch') U+17D2 any(c_out) + [K_BKSP] > nul c working OK + + + [K_NPSTAR] > U+002A + + [SHIFT K_NPSTAR] > U+002A + + [K_NPPLUS] > U+002B + + [SHIFT K_NPPLUS] > U+002B + + [K_NPMINUS] > U+002D + + [SHIFT K_NPMINUS] > U+002D + + [K_NPDOT] > U+002E + + [SHIFT K_NPDOT] > U+002E + + [K_NPSLASH] > U+002F + + [SHIFT K_NPSLASH] > U+002F + + + +c ==============================================NORMALIZATION============================================== + + + +match > use(normalise) + +group(normalise) + +c Illegitimate vowel combinations should be transformed to the legitimate ones (Case #6 and #7). + +U+17C1 U+17B6 > U+17C4 c when type េ and ា, tranform them to ោ +U+17B6 U+17C1 > U+17C4 c when type ា and េ, tranform them to ោ +U+17C1 U+17B8 > U+17BE c when type េ and ី , tranform them to ើ +U+17B8 U+17C1 > U+17BE c when type ី and េ, tranform them to ើ + +c Illegitimate vowel combinations should be transformed to the legitimate ones (Case #5). + +U+17C6 U+17BB > U+17BB U+17C6 c when type ំ and ុ , tranform them to ុំ +U+17C6 U+17B6 > U+17B6 U+17C6 c when type ំ and ា, tranform them to ាំ + + +c Rotate vowels + +any(v_gen) any(v_gen) > context(2) c the last vowel stroke replaces the previous one. + +c Rotation of two-part vowels + +any(v_gen) any(v_pseudo) any(v_gen) > context(3) c a two-part vowel rotates to a genuine vowel. +any(v_gen) any(v_pseudo) any(v_pseudo) > context(3) c a two-part vowel rotates to a pseudo vowel. +any(v_gen) any(v_gen) any(v_pseudo) > context(2) context(3) c a genuine vowel rotates to a two-part vowel. +any(v_pseudo) any(v_gen) any(v_pseudo) > context(2) context(3) c a pseudo vowel roates to a two-part vowel. +any(v_gen) any(v_pseudo) any(v_gen) any(v_pseudo) > context(3) context(4) c a two-part vowel rotates to another two-part vowel. +any(v_pseudo) any(v_pseudo) > context(2) c a pseudo vowel rotates to another pseudo vowel. +c any(v_pseudo) any(v_gen) > context(2) c a pseudo vowel roates to a genuine vowel. + +c Delete extraneous coeng markers + +U+17D2 U+17D2 > context(1) c no two coengs are allowed. +U+17D2 any(v_any) > context(2) c The coeng is deleted when a vowel is put after it. +U+17D2 any(v_gen) any(v_pseudo) > context(2) context(3) c The coeng is deleted when a pseudo vowel is put after it. + +c Case #1: when a subscript is typed after a vowel, the subscript should be automatically moved to before the vowel. + +any(v_any) U+17D2 any(subcons) > context(2) context(3) context(1) c a vowel has to come after a subscript. +any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c these two-part vowels have to come after a subscript. +any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c these two-part vowels have to come after a subscript. + +c a subscript placed after a shifter and a vowel (a genuine/pseudo vowel or a two-part vowel) has to move to before them. + +any(c_shifter) any(v_any) U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c after a genuine/pseudo vowel +any(c_shifter) any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c after a two-part vowel +any(c_shifter) any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c after a two-part vowel + +c Case #2: when [D2+9A] is typed before a subscript, it should be moved to after it. + +U+17D2 U+178A U+17D2 U+179A > context(1) U+178F context(3) context(4) c change subscript ្ដ to ្ត after ្រ +U+17D2 U+179A U+17D2 U+178A > context(3) U+178F context(1) context(2) c ្ដ placed after ្រ has to be changed to ្ត and placed before ្រ + +U+17D2 U+178A any(v_any) U+17D2 U+179A > context(1) U+178F context(4) context(5) context(3) c fixed 6e +U+17D2 U+178A any(v_combo_N) U+17C6 U+17D2 U+179A > context(1) U+178F context(5) context(6) context(3) context(4) +U+17D2 U+178A any(v_combo_R) U+17C7 U+17D2 U+179A > context(1) U+178F context(5) context(6) context(3) context(4) + +U+17D2 U+179A any(v_any) U+17D2 U+178A > context(1) U+178F context(4) context(2) context(3) c fixed 6g +U+17D2 U+179A any(v_combo_N) U+17C6 U+17D2 U+178A > context(1) U+178F context(5) context(2) context(3) context(4) +U+17D2 U+179A any(v_combo_R) U+17C7 U+17D2 U+178A > context(1) U+178F context(5) context(2) context(3) context(4) + +U+17D2 U+179A any(c_shifter) U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c when a shifter intervenes, move it to the end. +U+17D2 U+179A U+17D2 any(subcons) > context(3) context(4) context(1) context(2) c move any subscript placed after [D2+DA] to before them +U+17D2 U+179A any(v_any) U+17D2 any(subcons) > context(4) context(5) context(1) context(2) context(3) c when a vowel intervenes, move it to the end +U+17D2 U+179A any(v_combo_N) U+17C6 U+17D2 any(subcons) > context(5) context(6) context(1) context(2) context(3) context(4) c when a two-part vowel intervenes, move it to the end too. +U+17D2 U+179A any(v_combo_R) U+17C7 U+17D2 any(subcons) > context(5) context(6) context(1) context(2) context(3) context(4) c (same as stated right above) + +c when a subscript is placed after a consonant shifter, they should be reversed. (Case #3) + +any(c_shifter) U+17D2 any(subcons) > context(2) context(3) context(1) + +c Reordering - Triisap and Muusikatoan store before a vowel and a vowelcomboR and pseudo vowel. + +any(v_any) any(c_shifter) > context(2) context(1) c place either a enguine or pseudo vowel after a consonant shifter +any(v_gen) any(v_pseudo) any(c_shifter) > context(3) context(1) context(2) c place a two-part vowel after a consonant shifter(i.e. មោះ៉ ) +any(c_shifter) any(v_any) any(c_shifter) > context(3) context(2) c when either a genuine or pseudo vowel is in between two consonant shifters, replace the one on the left with the one on the right. +any(c_shifter) any(v_gen) any(v_pseudo) any(c_shifter) > context(2) context(3) context(4) c when a two-part vowel is in between two consonants shifter, replace the one on the left with the one the right. + +c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ +c + when [BB] is place either before or after an above vowel (ិ ី ឹ ឺ ើ and ាំ) and a Samyok Sannya (Case #4) + +c + the [BB] should transform to a respective consonant shifter placed before the vowel. + +c + the consonant shifter is a Trisap if the base consonant is ស ហ អ + +c + the consonant shifter is a Muusikatoan if the base consonant is ង ញ ម យ រ វ ប and ន ល + +c +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ + +c consonant clusters --1st > 2nd series (with U+17BB) + +any(c_combo_QA) U+17D2 U+17A2 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +any(c_combo_QA) U+17D2 U+17A2 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+179B U+17D2 any(c_1st_combo_LO) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+179B U+17D2 any(c_1st_combo_LO) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+1798 U+17D2 any(c_1st_combo_MO) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+179F U+17D2 U+1794 U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c prevent ស្បុិ from transforming to ស្ប៊ិ​​​, but produce ស្ប៉ិ instead because ប never have Triisap shifted down. +U+179F U+17D2 U+1794 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) +U+179F U+17D2 U+1794 any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) +U+179F U+17D2 U+1794 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) + +U+179F U+17D2 any(c_combo_SA) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+179F U+17D2 any(c_combo_SA) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+179F U+17D2 any(c_combo_SA) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+179F U+17D2 any(c_combo_SA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +any(c_combo_HA) U+17D2 U+17A0 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +any(c_combo_HA) U+17D2 U+17A0 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+17A2 U+17D2 U+1784 U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+17A2 U+17D2 U+1784 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+17A2 U+17D2 U+1784 any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+17A2 U+17D2 U+1784 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+17A2 U+17D2 U+179C U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+17A2 U+17D2 U+179C U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+17A2 U+17D2 U+179C any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) +U+17A2 U+17D2 U+179C U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) + +U+17A0 U+17D2 U+1794 U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c change U+17BB preceded by HA and coeng BA to U+17C9 before the above vowel +U+17A0 U+17D2 U+1794 U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(3) context(6) c change U+17BB preceded by HA and coeng BA to U+17C9 before ាំ +U+17A0 U+17D2 U+1794 any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) c change U+17BB preceded by HA and coeng BA to U+17C9 before the above vowel +U+17A0 U+17D2 U+1794 U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) c change U+17BB preceded by HA and coeng BA to U+17C9 before U+17B6 U+17C6 + + +U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB any(v_above) > context(1) context(2) context(3) U+17CA context(5) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before the above vowel +U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(3) context(6) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before ាំ +U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) any(v_above) U+17BB > context(1) context(2) context(3) U+17CA context(4) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before the above vowel +U+17A0 U+17D2 any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(6) context(7) c change U+17BB preceded by HA and coeng shiftable_c_2nd to U+17CA before U+17B6 U+17C6 + +c consonant clusters --2nd > 1st series (with U+17BB) + +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) +U+179B U+17D2 any(c_2nd_combo_LO) any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) + +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB any(v_above) > context(1) context(2) context(3) U+17C9 context(5) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) +U+1798 U+17D2 any(c_2nd_combo_MO) any(v_above) U+17BB > context(1) context(2) context(3) U+17C9 context(4) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C6 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(6) context(7) + +c single consonants --1st > 2nd series (with U+17BB) + +any(shiftable_c_1st) U+17BB any(v_above) > context(1) U+17CA context(3) c change U+17BB to U+17CA before the above vowel +any(shiftable_c_1st) U+17BB U+17B6 U+17C6 > context(1) U+17CA context(3) context(4) c change U+17BB to U+17CA before ាំ +any(shiftable_c_1st) any(v_above) U+17BB > context(1) U+17CA context(2) c change U+17BB to U+17CA and place it before the above vowel +any(shiftable_c_1st) U+17BB U+17C6 U+17B6 U+17C6 > context(1) U+17CA context(4) context(5) c change U+17BB U+17C6 to U+17CA + +c single consonants --2nd > 1st series (with U+17BB) + +any(shiftable_c_2nd_with_BA) U+17BB any(v_above) > context(1) U+17C9 context(3) c change U+17BB to U+17C9 before the above vowel +any(shiftable_c_2nd_with_BA) U+17BB U+17B6 U+17C6 > context(1) U+17C9 context(3) context(4) c change U+17BB to U+17C9 before ាំ +any(shiftable_c_2nd_with_BA) any(v_above) U+17BB > context(1) U+17C9 context(2) c change U+17BB to U+17C9 before the above vowel +any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 U+17C6 > context(1) U+17C9 context(4) context(5) c change U+17BB U+17C6 to U+17C9 + +c prevent incorrect use of consonant shifter with 2nd series consonant clusters (without U+17BB) + +U+179B U+17D2 any(c_2nd_combo_LO) U+17CA any(v_above) > context(1) context(2) context(3) U+17C9 context(5) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA any(v_above) > context(1) context(2) context(3) U+17C9 context(5) +U+179B U+17D2 any(c_2nd_combo_LO) U+17CA any(v_gen) any(v_pseudo) > context(1) context(2) context(3) U+17C9 context(5) context(6) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA any(v_gen) any(v_pseudo) > context(1) context(2) context(3) U+17C9 context(5) context(6) + +c single consonants --2nd > 1st series (without U+17BB) + +any(shiftable_c_2nd) U+17CA any(v_above) > context(1) U+17C9 context(3) c change U+17CA to U+17C9 before the above vowel +any(shiftable_c_2nd) U+17CA any(v_gen) any(v_pseudo) > context(1) U+17C9 context(3) context(4) c change U+17CA to U+17C9 before ាំ + +U+17D2 any(shiftable_c_2nd) U+17CA any(v_above) > context c stop ស្រ៊ី from becoming ស្រ៉ី +U+17D2 any(shiftable_c_2nd) U+17CA any(v_gen) any(v_pseudo) > context + +c SPECIAL CASE -- the following clusters may accept either of the two consonant shifters. + +U+1794 U+17D2 U+1799 any(c_shifter) > context +U+179F U+17D2 U+1794 any(c_shifter) > context +U+1786 U+17D2 U+1794 any(c_shifter) > context +U+1794 U+17D2 U+1799 any(c_shifter) > context +U+179F U+17D2 U+1794 any(c_shifter) > context +U+1786 U+17D2 U+1794 any(c_shifter) > context + +c single --1st > 2nd series (without U+17BB) + +any(shiftable_c_1st) U+17C9 any(v_above) > context(1) U+17CA context(3) c change U+17C9 to U+17CA before the above vowel +any(shiftable_c_1st) U+17C9 any(v_gen) any(v_pseudo) > context(1) U+17CA context(3) context(4) c change U+17C9 to U+17CA before ាំ + +c consonant clusters --1st > 2nd series (without U+17BB) + +any(c_combo_QA) U+17D2 U+17A2 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+179B U+17D2 any(c_1st_combo_LO) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+179F U+17D2 U+1794 U+17C9 any(v_above) > context(1) context(2) context(3) U+17C9 context(5) c prevent ស្ប៉ី from transforming to ស្ប៊ី +U+179F U+17D2 any(c_combo_SA) U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +any(c_combo_HA) U+17D2 U+17A0 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+17A2 U+17D2 U+1784 U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +U+17A2 U+17D2 U+179C U+17C9 any(v_above) > context(1) context(2) context(3) U+17CA context(5) +any(c_combo_QA) U+17D2 U+17A2 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+179B U+17D2 any(c_1st_combo_LO) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+179F U+17D2 U+1794 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17C9 context(5) context(6) c prevent ស្ប៉ាំ from transforming to ស្ប៊ាំ +U+179F U+17D2 any(c_combo_SA) U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +any(c_combo_HA) U+17D2 U+17A0 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+17A2 U+17D2 U+1784 U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) +U+17A2 U+17D2 U+179C U+17C9 U+17B6 U+17C6 > context(1) context(2) context(3) U+17CA context(5) context(6) + +c U+17B6 U+17BB and U+17C6 in that order preceeded by any first series consonant (cluster) are transformed to U+17CA U+17B6 and U+17C6 + +any(shiftable_c_1st) U+17B6 U+17BB U+17C6 > context(1) U+17CA context(2) context(4) +any(shiftable_c_1st) U+17BB U+17C6 U+17B6 > context(1) U+17CA context(4) context(3) + +any(c_combo_QA) U+17D2 U+17A2 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+179B U+17D2 any(c_1st_combo_LO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+1798 U+17D2 any(c_1st_combo_MO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+179F U+17D2 any(c_combo_SA) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+179F U+17D2 any(c_combo_SA) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +any(c_combo_HA) U+17D2 U+17A0 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+17A2 U+17D2 U+1784 U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+17A2 U+17D2 U+1784 U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(6) context(5) + +U+17A2 U+17D2 U+179C U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17CA context(4) context(6) +U+17A2 U+17D2 U+179C U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17CA context(4) context(6) + +c U+17B6 U+17BB and U+17C6 in that order preceeded by any second series consonant (cluster) are transformed to U+17C9 U+17B6 and U+17C6 + +any(shiftable_c_2nd_with_BA) U+17B6 U+17BB U+17C6 > context(1) U+17C9 context(2) context(4) +any(shiftable_c_2nd_with_BA) U+17BB U+17C6 U+17B6 > context(1) U+17C9 context(4) context(3) + +U+179B U+17D2 any(c_2nd_combo_LO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17C9 context(4) context(6) +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17C9 context(6) context(5) + +U+1798 U+17D2 any(c_2nd_combo_MO) U+17B6 U+17BB U+17C6 > context(1) context(2) context(3) U+17C9 context(4) context(6) +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C6 U+17B6 > context(1) context(2) context(3) U+17C9 context(6) context(5) + +c េុី/ុេី/៉េី > ៊ើ + +any(shiftable_c_1st) U+17C1 U+17BB U+17B8 > context(1) U+17CA U+17BE c cancel out by vowel rotation +any(shiftable_c_1st) U+17BB U+17C1 U+17B8 > context(1) U+17CA U+17BE c cancel out by vowel rotation +any(shiftable_c_1st) U+17C9 U+17C1 U+17B8 > context(1) U+17CA U+17BE + +any(c_combo_QA) U+17D2 U+17A2 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +any(c_combo_QA) U+17D2 U+17A2 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +any(c_combo_QA) U+17D2 U+17A2 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+179B U+17D2 any(c_1st_combo_LO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+179B U+17D2 any(c_1st_combo_LO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+179B U+17D2 any(c_1st_combo_LO) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+1798 U+17D2 any(c_1st_combo_MO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+1798 U+17D2 any(c_1st_combo_MO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+1798 U+17D2 any(c_1st_combo_MO) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+179F U+17D2 any(c_combo_SA) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+179F U+17D2 any(c_combo_SA) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+179F U+17D2 any(c_combo_SA) U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +any(c_combo_HA) U+17D2 U+17A0 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +any(c_combo_HA) U+17D2 U+17A0 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +any(c_combo_HA) U+17D2 U+17A0 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+17A2 U+17D2 U+1784 U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+17A2 U+17D2 U+1784 U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+17A2 U+17D2 U+1784 U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +U+17A2 U+17D2 U+179C U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+17A2 U+17D2 U+179C U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE c cancel out by vowel rotation +U+17A2 U+17D2 U+179C U+17C9 U+17C1 U+17B8 > context(1) context(2) context(3) U+17CA U+17BE + +c េុី/ុេី/៊េី > ៉ើ + +any(shiftable_c_2nd) U+17C1 U+17BB U+17B8 > context(1) U+17C9 U+17BE c cancel out by vowel rotation +any(shiftable_c_2nd) U+17BB U+17C1 U+17B8 > context(1) U+17C9 U+17BE c cancel out by vowel rotation +any(shiftable_c_2nd) U+17CA U+17C1 U+17B8 > context(1) U+17C9 U+17BE + +U+179B U+17D2 any(c_2nd_combo_LO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation +U+179B U+17D2 any(c_2nd_combo_LO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation +U+179B U+17D2 any(c_2nd_combo_LO) U+17CA U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE + +U+1798 U+17D2 any(c_2nd_combo_MO) U+17C1 U+17BB U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation +U+1798 U+17D2 any(c_2nd_combo_MO) U+17BB U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE c cancel out by vowel rotation +U+1798 U+17D2 any(c_2nd_combo_MO) U+17CA U+17C1 U+17B8 > context(1) context(2) context(3) U+17C9 U+17BE + +c place Robat after a base before any vowel + +any(c_out) any(v_gen) U+17CC > context(1) context(3) context(2) + +c Use appropriate identical subscript in two separate contexts. (Case #8) + +U+178E U+17D2 U+178F > context(1) context(2) U+178A +U+1793 U+17D2 U+178A > context(1) context(2) U+178F + +c BONUSES​(note that these combinations are invalid and they can't be found in the CN dictionary 1967 nor RH 1997, but users think they are) + +U+1791 U+17D2 U+1794 > U+17A1 c ទ្ប transforms to ឡ + +U+1794 U+17D2 U+1789 > U+17AB c ប្ញ transforms to ឫ +U+17AB U+17BB > U+17AC c ឫុ transforms to ឬ + +U+17AD U+17B6 > U+1789 c ឭា transforms to ញ +U+17AE U+17B6 > U+1789 c ឮា transforms to ញ + +U+1796 U+17D2 U+1789 > U+17AD c ព្ញ transforms to ឭ +U+17AD U+17BB > U+17AE c ឭុ transforms to ឮ + +U+1796 U+17D2 U+178B > U+17B0 c ព្ឋ transforms to ឰ + +U+17A7 U+17B7 > U+17B1 c ឧិ transforms to ឱ +U+17A7 U+17CC > U+17B1 c ឧ៌ transforms to ឱ +U+17A7 U+17CD > U+17B1 c ឧ៍ transforms to ឱ + +c U+17D4 U+179B U+17D4 > U+17D8 c ។ល។ transforms to U+17D8 + +U+178A U+17D2 U+1792 > U+178A U+17D2 U+178B c ដ្ធ > ដ្ឋ +U+1791 U+17D2 U+178B > U+1791 U+17D2 U+1792 c ទ្ឋ > ទ្ធ + +c additional rules + +U+1796 U+1793 U+17D2 U+178B > context(1) context(2) context(3) U+1792 c ពន្ឋ > ពន្ធ +U+1796 U+17D0 U+1793 U+17D2 U+178B > context(1) context(2) context(3) context(4) U+1792 c ព័ន្ឋ > ព័ន្ធ + + +U+17AA U+17D2 U+1799 > U+17B1 context(2) context(3) c ឪ្យ > ឱ្យ +U+17B3 U+17D2 U+1799 > U+17B1 context(2) context(3) c ឳ្យ > ឱ្យ + +U+1789 U+17D2 U+179C > U+1796 context(2) context(3) U+17B6 c ញ្វ (as in សញ្វវុធ) > ព្វា as in សញ្វវុធ + +U+17D2 U+1799 U+17C1 U+17BA > U+17BF c េ្យឺ > ឿ +U+17D2 U+1799 U+17C1 U+17B9 > U+17BF c េ្យឹ > ឿ +U+17D2 U+1799 U+17C1 U+17B8 > U+17BF c េ្យី > ឿ + +U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17BA > context(3) context(4) U+17BF c គ្រេ្យឺង > គ្រឿង +U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17B9 > context(3) context(4) U+17BF c គ្រេ្យឹង > គ្រឿង +U+17D2 U+1799 U+17D2 any(c_out) U+17C1 U+17B8 > context(3) context(4) U+17BF c គ្រេ្យីង > គ្រឿង + +c In everyday practice, both 'regular space' and 'no-break space' are not used before punctuation, i.e. ។ ! etc. +c An annoying issue of having line broken up at the end of the line dropping the punctuation (។, ! and also ៗ) to the next line. +c It may be helpful to include a rule where a 'Zero Width No-Break Space' should be output together with those punct. +c ref: https://www.unicode.org/L2/L2020/20008-core-text.pdf +c ref: https://www.unicode.org/versions/Unicode12.0.0/ch23.pdf#G12985 +c ref: https://www.compart.com/en/unicode/U+FEFF + +c EOF diff --git a/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kps b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kps new file mode 100644 index 00000000000..a6b5ca26ce8 --- /dev/null +++ b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kps @@ -0,0 +1,57 @@ + + + + 15.0.266.0 + 7.0 + + + + readme.htm + splash.gif + + + + + + + + + + Khmer Angkor + © 2015-2022 SIL International + Makara Sok + + https://keyman.com/keyboards/khmer_angkor + + + + ..\build\khmer_angkor.js + File khmer_angkor.js + 0 + .js + + + ..\build\khmer_angkor.kvk + File khmer_angkor.kvk + 0 + .kvk + + + ..\build\khmer_angkor.kmx + Keyboard Khmer Angkor + 0 + .kmx + + + + + Khmer Angkor + khmer_angkor + 1.3 + + Central Khmer (Khmer, Cambodia) + + + + + diff --git a/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kvks b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kvks new file mode 100644 index 00000000000..e9c38a464dd --- /dev/null +++ b/developer/src/kmc/test/fixtures/kpj-2.0/khmer_angkor/source/khmer_angkor.kvks @@ -0,0 +1,206 @@ + + +
+ 10.0 + khmer_angkor + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + ] + [ + / + . + + + + & + + * + @ + \ + } + { + - + ÷ + : + , + + ; + < + # + > + × + $ + +   + + + + + + + + + + + + + ᧿ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + « + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ! + + " + + % + + ( + ) + + = + + + ? + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +  + + + » + + + +
diff --git a/developer/src/kmc/tsconfig.json b/developer/src/kmc/tsconfig.json new file mode 100644 index 00000000000..41d2ab34286 --- /dev/null +++ b/developer/src/kmc/tsconfig.json @@ -0,0 +1,27 @@ +{ + "extends": "./tsconfig.kmc-base.json", + + "compilerOptions": { + "outDir": "build/src/", + "rootDir": "src/", + "baseUrl": ".", + "paths": { + "@keymanapp/common-types": [ "../../../common/web/types/src/main" ], + "@keymanapp/kmc-keyboard": [ "../kmc-keyboard/src/main" ], + "@keymanapp/kmc-model": [ "../kmc-model/src/main" ], + "@keymanapp/kmc-model-info": [ "../kmc-model-info/src/model-info-compiler" ], + "@keymanapp/kmc-package": [ "../kmc-package/src/kmp-compiler" ], + } + }, + "include": [ + "src/**/*.ts" + ], + "references": [ + { "path": "../../../common/web/keyman-version/tsconfig.esm.json" }, + { "path": "../../../common/web/types" }, + { "path": "../kmc-keyboard" }, + { "path": "../kmc-model" }, + { "path": "../kmc-model-info" }, + { "path": "../kmc-package" }, + ] +} diff --git a/developer/src/kmc/tsconfig.kmc-base.json b/developer/src/kmc/tsconfig.kmc-base.json new file mode 100644 index 00000000000..f8cff50b3fa --- /dev/null +++ b/developer/src/kmc/tsconfig.kmc-base.json @@ -0,0 +1,3 @@ +{ + "extends": "../../../tsconfig.esm-base.json", +} diff --git a/developer/src/kmcmpdll/Compiler.cpp b/developer/src/kmcmpdll/Compiler.cpp index 1fa94fdd59f..6289f95d11a 100644 --- a/developer/src/kmcmpdll/Compiler.cpp +++ b/developer/src/kmcmpdll/Compiler.cpp @@ -1275,6 +1275,7 @@ DWORD ProcessSystemStore(PFILE_KEYBOARD fk, DWORD SystemID, PFILE_STORE sp) 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; @@ -3336,7 +3337,12 @@ DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, HANDLE hOutfile) return CERR_SomewhereIGotItWrong; } - SetChecksum(buf, &ck->dwCheckSum, (DWORD)size); + 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); diff --git a/developer/src/kmcmpdll/kcframe/kcframe.vcxproj b/developer/src/kmcmpdll/kcframe/kcframe.vcxproj index cc92af4fc18..24cb578d733 100644 --- a/developer/src/kmcmpdll/kcframe/kcframe.vcxproj +++ b/developer/src/kmcmpdll/kcframe/kcframe.vcxproj @@ -114,7 +114,7 @@ MaxSpeed OnlyExplicitInline - .;..\..\..\global\inc;%(AdditionalIncludeDirectories) + .;..;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true MultiThreaded @@ -149,7 +149,7 @@ MaxSpeed OnlyExplicitInline - .;..\..\..\global\inc;%(AdditionalIncludeDirectories) + .;..;%(AdditionalIncludeDirectories) WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) true MultiThreaded @@ -182,7 +182,7 @@ Disabled - .;..\..\..\global\inc;%(AdditionalIncludeDirectories) + .;..;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) true EnableFastChecks @@ -218,7 +218,7 @@ Disabled - .;..\..\..\global\inc;%(AdditionalIncludeDirectories) + .;..;%(AdditionalIncludeDirectories) WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) EnableFastChecks MultiThreadedDebug diff --git a/developer/src/kmcomp/kmcomp.dpr b/developer/src/kmcomp/kmcomp.dpr index 8ac6e124cfa..30204a63dba 100644 --- a/developer/src/kmcomp/kmcomp.dpr +++ b/developer/src/kmcomp/kmcomp.dpr @@ -10,7 +10,6 @@ uses RegistryKeys in '..\..\..\common\windows\delphi\general\RegistryKeys.pas', kccompilepackage in 'kccompilepackage.pas', CompilePackage in '..\common\delphi\compiler\CompilePackage.pas', - CRC32 in '..\..\..\common\windows\delphi\general\CRC32.pas', kpsfile in '..\common\delphi\packages\kpsfile.pas', PackageInfo in '..\..\..\common\windows\delphi\packages\PackageInfo.pas', PackageFileFormats in '..\..\..\common\windows\delphi\packages\PackageFileFormats.pas', diff --git a/developer/src/kmcomp/kmcomp.dproj b/developer/src/kmcomp/kmcomp.dproj index f6f183330fb..36c16e35356 100644 --- a/developer/src/kmcomp/kmcomp.dproj +++ b/developer/src/kmcomp/kmcomp.dproj @@ -156,7 +156,6 @@ - diff --git a/developer/src/kmcomp/main.pas b/developer/src/kmcomp/main.pas index 1266bdba5d6..4aa29cf6c1a 100644 --- a/developer/src/kmcomp/main.pas +++ b/developer/src/kmcomp/main.pas @@ -47,6 +47,8 @@ implementation Winapi.ActiveX, Winapi.Windows, + KeyboardParser, + kmxfileconsts, Keyman.Developer.System.Project.ProjectLog, Keyman.Developer.System.Project.ProjectLogConsole, Keyman.Developer.System.ValidateRepoChanges, @@ -59,7 +61,8 @@ implementation CompileKeymanWeb, JsonExtractKeyboardInfo, ValidateKeyboardInfo, - MergeKeyboardInfo; + MergeKeyboardInfo, + UKeymanTargets; function CompileKeyboard(FInFile, FOutFile: string; FDebug, FWarnAsError: Boolean): Boolean; forward; // I4706 function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion: Boolean): Boolean; forward; @@ -332,28 +335,67 @@ function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion: end; function CompileKeyboard(FInFile, FOutFile: string; FDebug, FWarnAsError: Boolean): Boolean; // I4706 +var + FIsJS, FIsKMX: Boolean; + kp: TKeyboardParser; + FTargets: TKeymanTargets; begin - if SameText(ExtractFileExt(FOutFile), '.js') then + if ExtractFileExt(FOutFile) = '.*' then begin - with TCompileKeymanWeb.Create do + // Load the input .kmn and determine if it targets .js and .kmx + kp := TKeyboardParser.Create; try - Result := Compile(nil, FInFile, FOutFile, FDebug, @CompilerMessageW); // I3681 // I4865 // I4866 + 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 - Free; + kp.Free; end; end else begin - if FOutFile = '' then FOutFile := ChangeFileExt(FInFile, '.kmx'); - Result := CompileKeyboardFile(PChar(FInFile), PChar(FOutFile), FDebug, FWarnAsError, True, @CompilerMessage) <> 0; // I4865 // I4866 - Result := Result and CompileVisualKeyboardFromKMX(FInFile, FOutFile); + 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 TProjectLogConsole.Instance.HasWarning and FWarnAsError then Result := False; // I4706 + 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 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); + 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; end; procedure FixupPathSlashes(var path: string); diff --git a/developer/src/kmconvert/kmconvert.dpr b/developer/src/kmconvert/kmconvert.dpr index 233c07d43c6..3452b73d301 100644 --- a/developer/src/kmconvert/kmconvert.dpr +++ b/developer/src/kmconvert/kmconvert.dpr @@ -23,7 +23,6 @@ uses VKeys in '..\..\..\common\windows\delphi\general\VKeys.pas', WindowsLanguages in '..\common\delphi\general\WindowsLanguages.pas', GetOsVersion in '..\..\..\common\windows\delphi\general\GetOsVersion.pas', - CRC32 in '..\..\..\common\windows\delphi\general\CRC32.pas', KeyNames in '..\..\..\common\windows\delphi\general\KeyNames.pas', utildir in '..\..\..\common\windows\delphi\general\utildir.pas', TextFileFormat in '..\common\delphi\general\TextFileFormat.pas', diff --git a/developer/src/kmconvert/kmconvert.dproj b/developer/src/kmconvert/kmconvert.dproj index 48626bc67be..73defed445c 100644 --- a/developer/src/kmconvert/kmconvert.dproj +++ b/developer/src/kmconvert/kmconvert.dproj @@ -129,7 +129,6 @@ - diff --git a/developer/src/kmdecomp/crc32.cpp b/developer/src/kmdecomp/crc32.cpp deleted file mode 100644 index 26b25cf5697..00000000000 --- a/developer/src/kmdecomp/crc32.cpp +++ /dev/null @@ -1,69 +0,0 @@ -#include "pch.h" - -/* - * Instead of performing a straightforward calculation of the 32 bit - * CRC using a series of logical operations, this program uses the - * faster table lookup method. This routine is called once when the - * program starts up to build the table which will be used later - * when calculating the CRC values. - */ - -#define CRC32_POLYNOMIAL 0xEDB88320L - -unsigned long CRCTable[256]; - -static void BuildCRCTable(void) -{ - static BOOL TableBuilt = FALSE; - int i; - int j; - unsigned long crc; - - if(!TableBuilt) - { - for(i = 0; i <= 255; i++) - { - crc = i; - - for(j = 8; j > 0; j--) - if(crc & 1) crc = (crc >> 1) ^ CRC32_POLYNOMIAL; else crc >>= 1; - - CRCTable[i] = crc; - } - } -} - - -/* - * This routine calculates the CRC for a block of data using the - * table lookup method. It accepts an original value for the crc, - * and returns the updated value. - */ - -static unsigned long CalculateBufferCRC(unsigned long count, BYTE *p) -{ - unsigned long temp1; - unsigned long temp2; - unsigned long crc = 0xFFFFFFFFL; - - while (count-- != 0) - { - temp1 = ( crc >> 8 ) & 0x00FFFFFFL; - temp2 = CRCTable[((int) crc ^ *p++) & 0xff]; - crc = temp1 ^ temp2; - } - - return crc; -} - - -BOOL VerifyChecksum(LPBYTE buf, LPDWORD CheckSum, DWORD sz) -{ - DWORD tempcs; - - tempcs = *CheckSum; - *CheckSum = 0; - - BuildCRCTable(); - return tempcs == CalculateBufferCRC(sz, buf); -} diff --git a/developer/src/kmdecomp/kmdecomp.cpp b/developer/src/kmdecomp/kmdecomp.cpp index 819a63350e8..c6a085fe2e0 100644 --- a/developer/src/kmdecomp/kmdecomp.cpp +++ b/developer/src/kmdecomp/kmdecomp.cpp @@ -27,7 +27,6 @@ BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard, LPBYTE *lpBitmap, DWORD *cbBitmap); -extern BOOL VerifyChecksum(LPBYTE buf, LPDWORD CheckSum, DWORD sz); void Err(char *p); int SaveKeyboardSource(LPKEYBOARD kbd, LPBYTE lpBitmap, DWORD cbBitmap, char *filename, char *bmpfile); int run(int argc, char *argv[]); @@ -152,25 +151,21 @@ BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard, LPBYTE *lpBitmap, DWOR ckbp->dwFileVersion > VERSION_MAX) { /* Old or new version -- identify the desired program version */ - if(VerifyChecksum(buf, &kbp->dwCheckSum, sz)) - { - kbp->dpStoreArray = (LPSTORE) (buf + ckbp->dpStoreArray); - for(sp = kbp->dpStoreArray, i = 0; i < kbp->cxStoreArray; i++, sp++) - if(sp->dwSystemID == TSS_COMPILEDVERSION) - { - char buf2[256]; - wsprintf(buf2, "Wrong File Version: file version is %ls", ((PBYTE)kbp) + (INT_PTR)sp->dpString); - delete buf; - Err(buf2); - return FALSE; - } + kbp->dpStoreArray = (LPSTORE) (buf + ckbp->dpStoreArray); + for(sp = kbp->dpStoreArray, i = 0; i < kbp->cxStoreArray; i++, sp++) { + if(sp->dwSystemID == TSS_COMPILEDVERSION) + { + char buf2[256]; + wsprintf(buf2, "Wrong File Version: file version is %ls", ((PBYTE)kbp) + (INT_PTR)sp->dpString); + delete buf; + Err(buf2); + return FALSE; + } } delete buf; Err("Unknown File Version: try using the latest version of KMDECOMP"); return FALSE; } - if(!VerifyChecksum(buf, &kbp->dwCheckSum, sz)) { delete buf; Err("Bad Checksum in file"); return FALSE; } - kbp->dpStoreArray = (LPSTORE) (buf + ckbp->dpStoreArray); kbp->dpGroupArray = (LPGROUP) (buf + ckbp->dpGroupArray); diff --git a/developer/src/kmdecomp/kmdecomp.vcxproj b/developer/src/kmdecomp/kmdecomp.vcxproj index 19614cbd387..223ae72199e 100644 --- a/developer/src/kmdecomp/kmdecomp.vcxproj +++ b/developer/src/kmdecomp/kmdecomp.vcxproj @@ -141,12 +141,6 @@ NotUsing - - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) - %(PreprocessorDefinitions) - %(AdditionalIncludeDirectories) %(PreprocessorDefinitions) diff --git a/developer/src/kmdecomp/kmdecomp.vcxproj.filters b/developer/src/kmdecomp/kmdecomp.vcxproj.filters index e268df7a479..4b98e2ca719 100644 --- a/developer/src/kmdecomp/kmdecomp.vcxproj.filters +++ b/developer/src/kmdecomp/kmdecomp.vcxproj.filters @@ -15,9 +15,6 @@
- - Source Files - Source Files diff --git a/developer/src/kmlmc/.gitignore b/developer/src/kmlmc/.gitignore deleted file mode 100644 index bbbfd24cf57..00000000000 --- a/developer/src/kmlmc/.gitignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules/ -dist/ -dist-tests/ -tsconfig.tsbuildinfo -tests/tsconfig.tsbuildinfo diff --git a/developer/src/kmlmc/Makefile b/developer/src/kmlmc/Makefile deleted file mode 100644 index a1e5927f98e..00000000000 --- a/developer/src/kmlmc/Makefile +++ /dev/null @@ -1,28 +0,0 @@ -# -# Keyman Developer - Lexical Model Compiler Makefile -# - -!include ..\Defines.mak - -build: .virtual - $(GIT_BASH_FOR_KEYMAN) build.sh -test - -clean: .virtual -#TODO: move to build.sh `clean` target - -rd /s/q dist - -rd /s/q dist-tests - -del tsconfig.tsbuildinfo - -signcode: - @rem nothing to do - -wrap-symbols: - @rem nothing to do - -test-manifest: - @rem This target needed as dependency for KMDECOMP - -install: - @rem nothing to do - -!include ..\Target.mak diff --git a/developer/src/kmlmc/README.md b/developer/src/kmlmc/README.md deleted file mode 100644 index 93fb0facfd4..00000000000 --- a/developer/src/kmlmc/README.md +++ /dev/null @@ -1,54 +0,0 @@ -Keyman Developer -================ - -This package provides the following Keyman **command line tools**: - - - `kmlmc` — takes **lexical model sources** and compiles them in to - a **.js** 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]. - -In order to build [lexical models][], these tools must be built and -compiled. - -[lexical models]: https://github.com/keymanapp/lexical-models - - -Install -------- - -Install `kmlmc`, `kmlmp`, and `kmlmi` globally: - - npm install -g @keymanapp/lexical-model-compiler - -Usage ------ - -To compile a lexical model from its `.model.ts` source, use `kmlmc`: - - kmlmc my-lexical-model.model.ts --outFile my-lexical-model.js - -To see more command line options by using the `--help` option: - - kmlmc --help - kmlmp --help - kmlmi --help - -How to build from source ------------------------- - -Run `build.sh`: - - ./build.sh - - -How to run the tests --------------------- - - ./build.sh -test - diff --git a/developer/src/kmlmc/build.sh b/developer/src/kmlmc/build.sh deleted file mode 100755 index eeffd63fa3a..00000000000 --- a/developer/src/kmlmc/build.sh +++ /dev/null @@ -1,128 +0,0 @@ -#!/usr/bin/env bash -# -# Compiles the developer tools, including the language model compilers. -# - -# 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" -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" -## END STANDARD BUILD SCRIPT INCLUDE -EX_USAGE=64 - -# Where to find lexical model types. -LEXICAL_MODELS_TYPES="$KEYMAN_ROOT/common/models/types" - - -# Build the main script. -build () { - npm run build || builder_die "Could not build top-level JavaScript file." -} - -display_usage ( ) { - echo "Usage: $0 [-test] [-publish-to-npm]" - echo " $0 -help" - echo - echo " -help displays this screen and exits" - echo " -test runs unit tests after building" - echo " -tdd runs unit tests WITHOUT building" - echo " -skip-package-install, -S skip package installation" - echo " -publish-to-npm publishes the current version to the npm package index" - echo " -dry-run do build, etc, but don't actually publish" -} - -################################ Main script ################################ - -run_tests=0 -install_dependencies=1 -should_publish=0 -npm_dist_tag= -should_dry_run=0 - -# Process command-line arguments -while [[ $# -gt 0 ]] ; do - key="$1" - case $key in - -help|-h) - display_usage - exit - ;; - -dry-run) - should_dry_run=1 - ;; - -skip-package-install|-S) - install_dependencies=0 - ;; - -test) - run_tests=1 - ;; - -tdd) - run_tests=1 - install_dependencies=0 - ;; - -version) - echo "Warning: -version is now ignored" - ;; - -tier) - echo "Warning: -tier is now ignored" - ;; - -publish-to-npm) - should_publish=1 - ;; - *) - echo "$0: invalid option: $key" - display_usage - exit $EX_USAGE - esac - shift # past the processed argument -done - -# Dry run settings -if (( should_dry_run )); then - DRY_RUN=--dry-run -else - DRY_RUN= -fi - -# 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 - verify_npm_setup - # We need to build keyman-version and lm-worker with a script for now - "$KEYMAN_ROOT/common/web/keyman-version/build.sh" || builder_die "Could not build keyman-version" -fi - -build || builder_die "Compilation failed." -echo "Typescript compilation successful." - -if (( run_tests )); then - npm test || builder_die "Tests failed" -fi - -if (( should_publish )); then - if [[ $TIER == stable ]]; then - npm_dist_tag=latest - else - npm_dist_tag=$TIER - fi - - set_npm_version - - # 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. - # - # See `npm help publish` for more details. - echo "Publishing $DRY_RUN npm package with tag $npm_dist_tag" - npm publish $DRY_RUN --access public --tag $npm_dist_tag || builder_die "Could not publish $npm_dist_tag release." - - # For now, kmlmc will have responsibility for publishing keyman-version. In - # the future, we should probably have a top-level npm publish script that - # publishes all modules for a given release version - "$KEYMAN_ROOT/common/web/keyman-version/build.sh" publish $DRY_RUN -fi diff --git a/developer/src/kmlmc/bundle.sh b/developer/src/kmlmc/bundle.sh deleted file mode 100755 index 19c9180c093..00000000000 --- a/developer/src/kmlmc/bundle.sh +++ /dev/null @@ -1,145 +0,0 @@ -#!/usr/bin/env bash - -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/build/jq.inc.sh" -. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" - -display_usage() { - echo "Usage: $0 --build-path path" - echo " $0 --help" - echo - echo "This script is called from the Keyman Developer installer build" - echo "and is not intended for standalone use." - echo - echo " --help displays this screen and exits" - echo " --build-path path temporary path where archive will be built" -} - -BUILD_PATH= - -# Process command-line arguments -while [[ $# -gt 0 ]] ; do - key="$1" - case $key in - -help|-h) - display_usage - exit - ;; - --build-path) - shift - BUILD_PATH="$1" - ;; - *) - echo "$0: invalid option: $key" - display_usage - exit 64 - esac - shift # past the processed argument -done - -if [[ -z $BUILD_PATH ]]; then - echo "Parameter --build-path is required" - display_usage - exit 64 -fi - -KEYMAN_WIX_TEMP_BASE="$BUILD_PATH" -KEYMAN_WIX_TEMP_MODELCOMPILER="$BUILD_PATH/ModelCompiler" -KEYMAN_MODELCOMPILER_TEMP=`mktemp -d` - -# We copy the built model compiler into a temporary folder in order to prepare -# the node modules, as otherwise the workspace will interfere - -cp -R "$KEYMAN_ROOT/developer/src/kmlmc/"* "$KEYMAN_MODELCOMPILER_TEMP/" - -cd "$KEYMAN_MODELCOMPILER_TEMP" - -# We use `npm pack` to extract only the aspects of the model-compiler actually -# needed for distribution. While we could use npm-bundle or similar, that adds -# extra, unwanted cruft; our approach gives us more control of the set of files -# distributed with the Keyman Developer installer. -# -# For users on other operating systems, node.js is a dependency and the compiler -# can be installed with `npm install @keymanapp/lexical-model-compiler`. - -# We copy the files to a temp folder in order to exclude thumbs.db, .vs, etc -# from harvesting -rm -rf "$KEYMAN_WIX_TEMP_MODELCOMPILER" - -# Step 1 - npm-pack the model compiler package, then unpack it in our bundling -# directory. This automatically strips the package to its barebones. -set_npm_version -npm pack -mv keymanapp-lexical-model-compiler*.tgz kmlmc.tgz -mv kmlmc.tgz "$KEYMAN_WIX_TEMP_BASE" - -# We extract the npm-packed version of the model compiler in order to reproduce -# our needed bundle. -cd "$KEYMAN_WIX_TEMP_BASE" -tar xvzf kmlmc.tgz - -# Creates the directory referenced by $(KEYMAN_WIX_TEMP_MODELCOMPILER). -mv package ModelCompiler - -# Cleans up the npm pack artifacts, which are no longer needed. -rm kmlmc.tgz - -# Step 2 - the model compiler has one in-repo dependency that will also need to -# be packed. Managing other in-repo dependencies will be simpler; they install -# into ModelCompiler's extracted bundle. - -mkdir -p "$KEYMAN_MODELCOMPILER_TEMP/node_modules/@keymanapp/" -cp -R "$KEYMAN_ROOT/node_modules/@keymanapp/models-types" "$KEYMAN_MODELCOMPILER_TEMP/node_modules/@keymanapp/" - -cd "$KEYMAN_MODELCOMPILER_TEMP/node_modules/@keymanapp/models-types" -set_npm_version -npm pack -mv keymanapp-models-types*.tgz kmtypes.tgz -mv kmtypes.tgz "$KEYMAN_WIX_TEMP_MODELCOMPILER" - - -cp -R "$KEYMAN_ROOT/node_modules/@keymanapp/keyman-version" "$KEYMAN_MODELCOMPILER_TEMP/node_modules/@keymanapp/" - -cd "$KEYMAN_MODELCOMPILER_TEMP/node_modules/@keymanapp/keyman-version" -set_npm_version -npm pack -mv keymanapp-keyman-version*.tgz kmver.tgz -mv kmver.tgz "$KEYMAN_WIX_TEMP_MODELCOMPILER" - -# Step 3 - install just the bare essentials by using our packed local -# dependency, followed by all external production dependencies. -cd "$KEYMAN_WIX_TEMP_MODELCOMPILER" -# package-lock.json wasn't bundled; this is needed to keep dependency versions -# consistent. - -# as of npm v8.x, even though we are only working with `dependencies`, -# `devDependencies` is still checked, and as these modules are present in -# devDependencies but are only available when in the repo path, we need to -# remove them before attempting to continue. -# Yuck! ref: https://github.com/npm/cli/issues/3975#issuecomment-985305678 -# ref: https://github.com/npm/cli/issues/2921 -# can't use npm uninstall because it depends on @keymanapp/models-types being -# present! - -cat "$KEYMAN_MODELCOMPILER_TEMP/package.json" | "$JQ" \ - '. | del(.devDependencies."@keymanapp/models-templates") | del(.devDependencies."@keymanapp/keyman-version") | del(.devDependencies."@keymanapp/models-wordbreakers")' \ - > "package.json" - -npm install kmtypes.tgz --production --no-optional -npm install kmver.tgz --production --no-optional -npm install --production --no-optional - -# Clean up the npm pack artifacts for the dependencies. -rm kmtypes.tgz -rm kmver.tgz - -# We don't need the unit tests -rm -rf "$KEYMAN_WIX_TEMP_MODELCOMPILER/tests" -rm -rf "$KEYMAN_MODELCOMPILER_TEMP" \ No newline at end of file diff --git a/developer/src/kmlmc/source/package-compiler/kmp-compiler.ts b/developer/src/kmlmc/source/package-compiler/kmp-compiler.ts deleted file mode 100644 index f9297435d36..00000000000 --- a/developer/src/kmlmc/source/package-compiler/kmp-compiler.ts +++ /dev/null @@ -1,163 +0,0 @@ -/// -/// - -import * as fs from 'fs'; -import * as path from 'path'; -import * as xml2js from 'xml2js'; -import * as JSZip from 'jszip'; - -let zip = JSZip(); - -export default class KmpCompiler { - - private kpsFilename: string; - - public transformKpsToKmpObject(kpsString: string, kpsFilename: string): KmpJsonFile { - - // Load the KPS data from XML as JS structured data. - - let kpsPackage = (() => { - let a: KpsPackage; - let parser = new xml2js.Parser({ - tagNameProcessors: [xml2js.processors.firstCharLowerCase], - explicitArray: false - }); - parser.parseString(kpsString, (e: unknown, r: unknown) => { a = r as KpsPackage }); - return a; - })(); - - let kps: KpsFile = kpsPackage.package; - - this.kpsFilename = kpsFilename; - - // - // To convert to kmp.json, we need to: - // - // 1. Unwrap arrays (and convert to array where single object) - // 2. Fix casing on `iD` - // 3. Rewrap info, keyboard.languages, lexicalModel.languages, startMenu.items elements - // 4. Remove options.followKeyboardVersion, file.fileType - // 5. Convert file.copyLocation to a Number - // 6. Filenames need to be basenames (but this comes after processing) - // - - // Helper functions - - let kpsInfoToKmpInfo = function (info: KpsFileInfo): KmpJsonFileInfo { - let ni: KmpJsonFileInfo = {}; - - let keys: (keyof KpsFileInfo)[] = ['author', 'copyright', 'name', 'version', 'website']; - for (let element of keys) { - if (info[element]) { - ni[element] = {description: info[element]._ || info[element].toString()}; - if(info[element].$ && info[element].$.URL) ni[element].url = info[element].$.URL; - } - } - return ni; - }; - - let arrayWrap = function(a: unknown) { - if (Array.isArray(a)) { - return a; - } - return [a]; - }; - - let kpsLanguagesToKmpLanguages = function(language: KpsFileLanguage[]): KmpJsonFileLanguage[] { - return language.map((element) => { return { name: element._, id: element.$.ID } }); - }; - - // Start to construct the kmp.json file from the .kps file - - let kmp: KmpJsonFile = { - system: kps.system, - options: {} - }; - - // Fill in additional fields - - let keys: (keyof KpsFileOptions & keyof KmpJsonFileOptions)[] = ['executeProgram', 'graphicFile', 'msiFilename', 'msiOptions', 'readmeFile']; - for (let element of keys) { - if (kps.options[element]) { - kmp.options[element] = kps.options[element]; - } - } - - if(kps.info) { - kmp.info = kpsInfoToKmpInfo(kps.info); - } - - if(kps.files && kps.files.file) { - kmp.files = arrayWrap(kps.files.file).map((file: KpsFileContentFile) => { - return { - name: file.name, - description: file.description, - copyLocation: parseInt(file.copyLocation, 10) - // note: we don't emit fileType as that is not permitted in kmp.json - } - }); - } - - if(kps.keyboards && kps.keyboards.keyboard) { - kmp.keyboards = arrayWrap(kps.keyboards.keyboard).map((keyboard: KpsFileKeyboard) => { - return { name:keyboard.name, id:keyboard.iD, version:keyboard.version, languages: kpsLanguagesToKmpLanguages(arrayWrap(keyboard.languages.language) as KpsFileLanguage[]) } - }); - } - - if(kps.lexicalModels && kps.lexicalModels.lexicalModel) { - kmp.lexicalModels = arrayWrap(kps.lexicalModels.lexicalModel).map((model: KpsFileLexicalModel) => { - return { name:model.name, id:model.iD, languages: kpsLanguagesToKmpLanguages(arrayWrap(model.languages.language) as KpsFileLanguage[]) } - }); - } - - if(kps.startMenu) { - kmp.startMenu = {}; - if(kps.startMenu.addUninstallEntry) kmp.startMenu.addUninstallEntry = kps.startMenu.addUninstallEntry === ''; - if(kps.startMenu.folder) kmp.startMenu.folder = kps.startMenu.folder; - if(kps.startMenu.items && kps.startMenu.items.item) kmp.startMenu.items = arrayWrap(kps.startMenu.items.item); - } - - if(kps.strings && kps.strings.string) { - kmp.strings = arrayWrap(kps.strings.string); - } - - //let util = require('util'); - //console.log(util.inspect(kmp, false, null)) - //console.log(kps); - - return kmp; - } - - /** - * Returns a Promise to the serialized data which can then be written to a .kmp file. - * - * @param kmpJsonData - The kmp.json Object - */ - public buildKmpFile(kmpJsonData: KmpJsonFile): Promise { - const kmpJsonFileName = 'kmp.json'; - - if(!kmpJsonData.files) { - kmpJsonData.files = []; - } - - const kpsPath = path.dirname(this.kpsFilename); - - kmpJsonData.files.forEach(function(value) { - // Make file path slashes compatible across platforms - const filename = value.name.replace(/\\/g, "/"); - const fullFilename = path.resolve(kpsPath, filename); - const baseFilename = path.basename(filename); - - const data = fs.readFileSync(fullFilename, 'utf8'); - zip.file(baseFilename, data); - - // Remove path data from files before save - value.name = baseFilename; - }); - - zip.file(kmpJsonFileName, JSON.stringify(kmpJsonData, null, 2)); - - // Generate kmp file - return zip.generateAsync({type: 'binarystring', compression:'DEFLATE'}); - } -} diff --git a/developer/src/kmlmc/tests/tsconfig.json b/developer/src/kmlmc/tests/tsconfig.json deleted file mode 100644 index 521ddc6c3ba..00000000000 --- a/developer/src/kmlmc/tests/tsconfig.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "extends": "../../../../tsconfig-base.json", - - "compilerOptions": { - "module": "commonjs", - "moduleResolution": "node", - "noImplicitAny": true, - "sourceMap": true, - "rootDir": "./", - "outDir": "../dist-tests/" - }, - "include": [ - "**/test-*.ts", - "helpers/index.ts" - ], - "references": [ - { "path": "../../../../common/web/keyman-version" }, - { "path": "../../../../common/models/types" }, - { "path": "../../../../common/models/templates" }, - { "path": "../../../../common/models/wordbreakers" }, - { "path": "../"} - ] -} \ No newline at end of file diff --git a/developer/src/kmlmc/tsconfig.json b/developer/src/kmlmc/tsconfig.json deleted file mode 100644 index 37d642b8c19..00000000000 --- a/developer/src/kmlmc/tsconfig.json +++ /dev/null @@ -1,29 +0,0 @@ -{ - "extends": "../../../tsconfig-base.json", - - "compilerOptions": { - "module": "commonjs", - "target": "es2017", - "moduleResolution": "node", - "sourceMap": true, - "outDir": "dist/", - "rootDir": "source/", - "declaration": true, - "alwaysStrict": true, - "noImplicitThis": true, - "noImplicitReturns": true, - "noImplicitAny": true, - "strictBindCallApply": true, - "strictFunctionTypes": true, - "noUnusedLocals": true - }, - "include": [ - "source/**/*.ts" - ], - "references": [ - { "path": "../../../common/web/keyman-version" }, - { "path": "../../../common/models/types" }, - { "path": "../../../common/models/templates" }, - { "path": "../../../common/models/wordbreakers" }, - ] -} diff --git a/developer/src/test/auto/Makefile b/developer/src/test/auto/Makefile index cf14e7cf183..635d933bd10 100644 --- a/developer/src/test/auto/Makefile +++ b/developer/src/test/auto/Makefile @@ -23,9 +23,13 @@ developer-tests: \ kmcomp-x64-structures \ kmconvert \ kmx-file-languages \ - lexical-model-compiler \ model-ts-parser \ - package-info + package-info \ + kmc \ + kmc-keyboard \ + kmc-model \ + kmc-model-info \ + kmc-package # ---------------------------------------------------------------------- @@ -66,11 +70,6 @@ kmconvert: .virtual cd $(DEVELOPER_ROOT)\src\test\auto\kmconvert $(MAKE) $(TARGET) -lexical-model-compiler: .virtual -# TODO: move this to a test target in kmlmc - cd $(DEVELOPER_ROOT)\src\kmlmc - $(GIT_BASH_FOR_KEYMAN) build.sh -test - model-ts-parser: .virtual rem TODO # cd $(DEVELOPER_ROOT)\src\test\auto\model-ts-parser @@ -80,6 +79,30 @@ package-info: .virtual cd $(DEVELOPER_ROOT)\src\test\auto\package-info $(MAKE) $(TARGET) +# ---------------------------------------------------------------------- +# Tests outside the test/auto folder +# ---------------------------------------------------------------------- + +kmc: .virtual + cd $(DEVELOPER_ROOT)\src\kmc + $(MAKE) $(TARGET) + +kmc-keyboard: .virtual + cd $(DEVELOPER_ROOT)\src\kmc-keyboard + $(MAKE) $(TARGET) + +kmc-model: .virtual + cd $(DEVELOPER_ROOT)\src\kmc-model + $(MAKE) $(TARGET) + +kmc-model-info: .virtual + cd $(DEVELOPER_ROOT)\src\kmc-model-info + $(MAKE) $(TARGET) + +kmc-package: .virtual + cd $(DEVELOPER_ROOT)\src\kmc-package + $(MAKE) $(TARGET) + # ---------------------------------------------------------------------- !include ..\..\Target.mak diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr index bfe61aa975b..6122f21916e 100644 --- a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr +++ b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dpr @@ -39,7 +39,6 @@ uses VKeys in '..\..\..\..\..\common\windows\delphi\general\VKeys.pas', ErrorControlledRegistry in '..\..\..\..\..\common\windows\delphi\vcl\ErrorControlledRegistry.pas', kmxfile in '..\..\..\..\..\common\windows\delphi\keyboards\kmxfile.pas', - CRC32 in '..\..\..\..\..\common\windows\delphi\general\CRC32.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', diff --git a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj index c1d5645e404..3bbafa79bf8 100644 --- a/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj +++ b/developer/src/test/auto/keyboard-package-versions/KeyboardPackageVersionsTestSuite.dproj @@ -120,7 +120,6 @@ - diff --git a/developer/src/test/auto/kmx-file-languages/KMXFileLanguagesTestSuite.dpr b/developer/src/test/auto/kmx-file-languages/KMXFileLanguagesTestSuite.dpr index aef131a3b0a..9e169bf18b2 100644 --- a/developer/src/test/auto/kmx-file-languages/KMXFileLanguagesTestSuite.dpr +++ b/developer/src/test/auto/kmx-file-languages/KMXFileLanguagesTestSuite.dpr @@ -13,7 +13,6 @@ uses DUnitX.TestFramework, Keyman.System.KMXFileLanguages in '..\..\..\common\delphi\keyboards\Keyman.System.KMXFileLanguages.pas', kmxfile in '..\..\..\..\..\common\windows\delphi\keyboards\kmxfile.pas', - CRC32 in '..\..\..\..\..\common\windows\delphi\general\CRC32.pas', KeyNames in '..\..\..\..\..\common\windows\delphi\general\KeyNames.pas', utildir in '..\..\..\..\..\common\windows\delphi\general\utildir.pas', utilfiletypes in '..\..\..\..\..\common\windows\delphi\general\utilfiletypes.pas', diff --git a/developer/src/test/auto/kmx-file-languages/KMXFileLanguagesTestSuite.dproj b/developer/src/test/auto/kmx-file-languages/KMXFileLanguagesTestSuite.dproj index 8c2fcb37ee9..3dfc3123141 100644 --- a/developer/src/test/auto/kmx-file-languages/KMXFileLanguagesTestSuite.dproj +++ b/developer/src/test/auto/kmx-file-languages/KMXFileLanguagesTestSuite.dproj @@ -93,7 +93,6 @@ - diff --git a/developer/src/tike/Makefile b/developer/src/tike/Makefile index c58816384c9..f9a4c61d367 100644 --- a/developer/src/tike/Makefile +++ b/developer/src/tike/Makefile @@ -11,6 +11,9 @@ 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) if exist $(WIN32_TARGET_PATH)\tike.dbg $(COPY) $(WIN32_TARGET_PATH)\tike.dbg $(DEVELOPER_DEBUGPATH) @@ -31,15 +34,15 @@ pull-core: cd $(KEYMAN_ROOT)\core !ifdef GIT_BASH_FOR_KEYMAN !ifdef DEBUG - $(GIT_BASH_FOR_KEYMAN) build.sh --debug + $(GIT_BASH_FOR_KEYMAN) build.sh --no-tests clean:x86 configure:x86 build:x86 --debug !else - $(GIT_BASH_FOR_KEYMAN) build.sh + $(GIT_BASH_FOR_KEYMAN) build.sh --no-tests clean:x86 configure:x86 build:x86 !endif !else !ifdef DEBUG - start /wait ./build.sh --debug + start /wait ./build.sh --no-tests clean:x86 configure:x86 build:x86 --debug !else - start /wait ./build.sh + start /wait ./build.sh --no-tests clean:x86 configure:x86 build:x86 !endif !endif diff --git a/developer/src/tike/child/UfrmDebug.pas b/developer/src/tike/child/UfrmDebug.pas index 3c3534aab89..615cfebc809 100644 --- a/developer/src/tike/child/UfrmDebug.pas +++ b/developer/src/tike/child/UfrmDebug.pas @@ -476,7 +476,7 @@ function TfrmDebug.ProcessKeyEvent(var Message: TMessage): Boolean; if not SetKeyEventContext then Exit(False); - if km_kbp_process_event(FDebugCore.State, vkey, modifier, 1) = KM_KBP_STATUS_OK then + if km_kbp_process_event(FDebugCore.State, Message.WParam, modifier, 1, KM_KBP_EVENT_FLAG_DEFAULT) = KM_KBP_STATUS_OK then begin // Process keystroke -- true = swallow keystroke Result := True; diff --git a/developer/src/tike/kmc.cmd b/developer/src/tike/kmc.cmd new file mode 100644 index 00000000000..a25878b4168 --- /dev/null +++ b/developer/src/tike/kmc.cmd @@ -0,0 +1,20 @@ +@echo off +setlocal +rem This script is based on /developer/src/node/inst/kmc.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\kmc.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\kmc.js" +) else ( + rem Cannot find node or kmc.js relative to execution path + echo Error: node.exe or kmc.js not found. + exit /b 1 +) + +%nodeexe% --enable-source-maps %nodecli% %* +exit /b %errorlevel% diff --git a/developer/src/tike/kmlmc.cmd b/developer/src/tike/kmlmc.cmd index 5572fbeb945..4f86f2e3bd0 100644 --- a/developer/src/tike/kmlmc.cmd +++ b/developer/src/tike/kmlmc.cmd @@ -1,20 +1,20 @@ @echo off setlocal -rem This script is a rough duplicate of /developer/src/node/inst/kmlmc.cmd. It is stored here +rem This script is based on /developer/src/node/inst/kmlmc.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 windows/src/developer/x/: + rem If running in developer/src/tike/: set nodeexe="%~dp0..\inst\node\dist\node.exe" - set nodecli="%~dp0..\kmlmc\dist\kmlmc.js" + set nodecli="%~dp0..\kmc\build\src\kmlmc.js" ) else if exist "%~dp0..\src\inst\node\dist\node.exe" ( - rem If running in windows/bin/developer/: + rem If running in developer/bin/: set nodeexe="%~dp0..\src\inst\node\dist\node.exe" - set nodecli="%~dp0..\src\kmlmc\dist\kmlmc.js" + set nodecli="%~dp0..\src\kmc\build\src\kmlmc.js" ) else ( rem Cannot find node or kmlmc.js relative to execution path echo Error: node.exe or kmlmc.js not found. exit /b 1 ) -%nodeexe% %nodecli% %* +%nodeexe% --enable-source-maps %nodecli% %* exit /b %errorlevel% diff --git a/developer/src/tike/kmlmi.cmd b/developer/src/tike/kmlmi.cmd index 197e04f3933..32f6a256a95 100644 --- a/developer/src/tike/kmlmi.cmd +++ b/developer/src/tike/kmlmi.cmd @@ -1,20 +1,20 @@ @echo off setlocal -rem This script is a rough duplicate of /developer/src/node/inst/kmlmi.cmd. It is stored here +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..\..\..\..\developer\src\inst\node\dist\node.exe" ( - rem If running in windows/src/developer/x/: - set nodeexe="%~dp0..\..\..\..\developer\src\inst\node\dist\node.exe" - set nodecli="%~dp0..\..\..\..\developer\src\kmlmc\dist\kmlmi.js" -) else if exist "%~dp0..\..\..\developer\src\inst\node\dist\node.exe" ( - rem If running in windows/bin/developer/: - set nodeexe="%~dp0..\..\..\developer\src\inst\node\dist\node.exe" - set nodecli="%~dp0..\..\..\developer\src\kmlmc\dist\kmlmi.js" +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% %nodecli% %* +%nodeexe% --enable-source-maps %nodecli% %* exit /b %errorlevel% diff --git a/developer/src/tike/kmlmp.cmd b/developer/src/tike/kmlmp.cmd index e9c28eba57e..716a5bf9401 100644 --- a/developer/src/tike/kmlmp.cmd +++ b/developer/src/tike/kmlmp.cmd @@ -1,20 +1,20 @@ @echo off setlocal -rem This script is a rough duplicate of /developer/src/node/inst/kmlmp.cmd. It is stored here +rem This script is based on /developer/src/node/inst/kmlmp.cmd. It is stored here rem in order to allow TIKE to call out to the compiler while debugging. -if exist "%~dp0..\..\..\..\developer\src\inst\node\dist\node.exe" ( - rem If running in windows/src/developer/x/: - set nodeexe="%~dp0..\..\..\..\developer\src\inst\node\dist\node.exe" - set nodecli="%~dp0..\..\..\..\developer\src\kmlmc\dist\kmlmp.js" -) else if exist "%~dp0..\..\..\developer\src\inst\node\dist\node.exe" ( - rem If running in windows/bin/developer/: - set nodeexe="%~dp0..\..\..\developer\src\inst\node\dist\node.exe" - set nodecli="%~dp0..\..\..\developer\src\kmlmc\dist\kmlmp.js" +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\kmlmp.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\kmlmp.js" ) else ( rem Cannot find node or kmlmp.js relative to execution path echo Error: node.exe or kmlmp.js not found. exit /b 1 ) -%nodeexe% %nodecli% %* +%nodeexe% --enable-source-maps %nodecli% %* exit /b %errorlevel% diff --git a/developer/src/tike/main/Keyman.System.KeymanCore.pas b/developer/src/tike/main/Keyman.System.KeymanCore.pas index f89d1d79b29..29a2341b182 100644 --- a/developer/src/tike/main/Keyman.System.KeymanCore.pas +++ b/developer/src/tike/main/Keyman.System.KeymanCore.pas @@ -312,9 +312,14 @@ function state: pkm_kbp_state; vk: km_kbp_virtual_key; modifier_state: uint16_t; - is_key_down: uint8_t + is_key_down: uint8_t; + event_flags: uint16_t ): km_kbp_status; cdecl; external kmnkbp0 delayed; +const + KM_KBP_EVENT_FLAG_DEFAULT = 0; // default value: hardware + KM_KBP_EVENT_FLAG_TOUCH = 1; // set if the event is touch, otherwise hardware + // keyboardprocessor_vkeys.h diff --git a/developer/src/tike/main/Keyman.System.VisualKeyboardImportKMX.pas b/developer/src/tike/main/Keyman.System.VisualKeyboardImportKMX.pas index b1536bd0a84..e36216e1559 100644 --- a/developer/src/tike/main/Keyman.System.VisualKeyboardImportKMX.pas +++ b/developer/src/tike/main/Keyman.System.VisualKeyboardImportKMX.pas @@ -110,7 +110,7 @@ procedure TVisualKeyboardImportKMX.ImportKey(vk: TVKKey); begin context := km_kbp_state_context(FCore.State); km_kbp_context_clear(context); - if km_kbp_process_event(FCore.State, vk.vkey, vk.kmshift, 1) = KM_KBP_STATUS_OK then + if km_kbp_process_event(FCore.State, vk.vkey, vk.kmshift, 1, KM_KBP_EVENT_FLAG_DEFAULT) = KM_KBP_STATUS_OK then begin FEvents.Clear; FEvents.AddStateItems(FCore.State, vk.vkey, vk.kmshift); diff --git a/developer/src/tike/main/KeymanDeveloperUtils.pas b/developer/src/tike/main/KeymanDeveloperUtils.pas index f2df2432c1b..25db17d1b5a 100644 --- a/developer/src/tike/main/KeymanDeveloperUtils.pas +++ b/developer/src/tike/main/KeymanDeveloperUtils.pas @@ -91,7 +91,7 @@ implementation Classes, SysUtils, ErrorControlledRegistry, ActiveX, shlobj, RegistryKeys, //Dialogs, utilsystem, Forms, kmxfile, Dialogs, utilexecute, - KeymanVersion, CRC32, VisualKeyboard, Controls; + KeymanVersion, VisualKeyboard, Controls; function GetKMShellPath(var ps: string): Boolean; forward; // I3655 diff --git a/developer/src/tike/tike.dpr b/developer/src/tike/tike.dpr index f047bfa9d4f..1a1b1a07fed 100644 --- a/developer/src/tike/tike.dpr +++ b/developer/src/tike/tike.dpr @@ -18,7 +18,6 @@ uses UfrmKeyTest in 'debug\UfrmKeyTest.pas' {frmKeyTest}, CompilePackage in '..\common\delphi\compiler\CompilePackage.pas', KeymanDeveloperUtils in 'main\KeymanDeveloperUtils.pas', - CRC32 in '..\..\..\common\windows\delphi\general\CRC32.pas', UfrmEditor in 'child\UfrmEditor.pas' {frmEditor}, MenuImgList in '..\common\delphi\components\MenuImgList.pas', UfrmSelectKey in 'dialogs\UfrmSelectKey.pas' {frmSelectKey}, diff --git a/developer/src/tike/tike.dproj b/developer/src/tike/tike.dproj index b1d8ce81f5e..46661e39ce1 100644 --- a/developer/src/tike/tike.dproj +++ b/developer/src/tike/tike.dproj @@ -141,7 +141,6 @@ -
frmEditor
diff --git a/docs/build/windows.md b/docs/build/windows.md index 096fd764b76..25889345cf3 100644 --- a/docs/build/windows.md +++ b/docs/build/windows.md @@ -134,7 +134,7 @@ PowerShell. * git for Windows * jq * Python 3 -* Meson 0.56+ +* Meson 1.0+ * Ninja * Pandoc @@ -144,7 +144,7 @@ PowerShell. $ProgressPreference = 'SilentlyContinue' choco install git jq python ninja pandoc refreshenv -# choco meson (0.55) is too old, 0.56 required: +# choco meson (0.55) is too old, 1.0 required: python -m pip install meson **Environment variables**: diff --git a/docs/npm-packages.md b/docs/npm-packages.md index 2a3cea707dd..221180cfe89 100644 --- a/docs/npm-packages.md +++ b/docs/npm-packages.md @@ -8,10 +8,18 @@ Currently, we're using TeamCity for this. Packages are **never** published from a developer's machine. -## List of current npm packages +## List of current published npm packages -* `@keymanapp/models-types` -- located at `common/models-types` -* `@keymanapp/lexical-model-compiler` -- located at `developer/src/kmlmc` +* `@keymanapp/models-types` -- located at `common/models/types` +* `@keymanapp/kmc` -- located at `developer/src/kmc` +* `@keymanapp/kmc-keyboard` -- located at `developer/src/kmc-keyboard` +* `@keymanapp/kmc-model` -- located at `developer/src/kmc-model` +* `@keymanapp/kmc-model-info` -- located at `developer/src/kmc-model-info` +* `@keymanapp/kmc-package` -- located at `developer/src/kmc-package` + +### Deprecated npm packages + +* `@keymanapp/lexical-model-compiler` -- replaced by `@keymanapp/kmc` ## For CI developers @@ -23,56 +31,68 @@ server. This is typically done with: ```bash -./build.sh -test +./build.sh configure build test ``` -Once the build succeeds and the tests pass, the version is set in +Once the build succeeds and the tests pass, you can publish! Use the following +script for this (**only on the CI server**!); during this, the version is set in `package.json`: ```bash -./build.sh -version 12.0.${BUILD_NUMBER} -tier ${tier} -``` - -Now you can publish! Use the following script for this (again, **only on the CI server**!) - -```bash -./build.sh -publish-to-npm -tier ${tier} +./build.sh publish ``` -It is then uploaded to the npm package directory. **Ensure that the -compiled sources are included in the tarball**. For example, +It is then uploaded to the npm package directory. **Ensure that the compiled +sources are included in the tarball**. For example: ```bash -$ npm publish -npm notice 📦 @keymanapp/lexical-model-compiler@0.0.0 +$ ./build.sh publish +npm notice +npm notice 📦 @keymanapp/kmc@16.0.61-alpha-local npm notice === Tarball Contents === -npm notice 12.4kB dist/lexical-model-compiler/build-trie.js -npm notice 1.2kB dist/kmlmc.js -npm notice 3.0kB dist/kmlmi.js -npm notice 1.4kB dist/kmlmp.js -npm notice 5.0kB dist/package-compiler/kmp-compiler.js -npm notice 55B dist/package-compiler/kmp-json-file.js -npm notice 552B dist/package-compiler/kps-file.js -npm notice 4.6kB dist/lexical-model-compiler/lexical-model-compiler.js -npm notice 315B dist/lexical-model-compiler/lexical-model.js -npm notice 158B dist/util/min-keyman-version.js -npm notice 4.5kB dist/model-info-compiler/model-info-compiler.js -npm notice 57B dist/lexical-model-compiler/model-info-file.js -npm notice 341B dist/lexical-model-compiler/stub.js -npm notice 115B dist/util/sysexits.js -npm notice 2.1kB dist/util/util.js -npm notice 1.4kB package.json -npm notice 1.0kB README.md +npm notice 841B Makefile +npm notice 2.8kB README.md +npm notice 2.5kB build.sh +npm notice 116B build/src/kmc.d.ts +npm notice 114B build/src/kmc.d.ts.map +npm notice 2.8kB build/src/kmc.js +npm notice 2.6kB build/src/kmc.js.map +npm notice 120B build/src/kmlmc.d.ts +npm notice 118B build/src/kmlmc.d.ts.map +npm notice 1.4kB build/src/kmlmc.js +npm notice 1.4kB build/src/kmlmc.js.map +npm notice 131B build/src/kmlmi.d.ts +npm notice 118B build/src/kmlmi.d.ts.map +npm notice 3.1kB build/src/kmlmi.js +npm notice 2.7kB build/src/kmlmi.js.map +npm notice 128B build/src/kmlmp.d.ts +npm notice 118B build/src/kmlmp.d.ts.map +npm notice 1.6kB build/src/kmlmp.js +npm notice 1.5kB build/src/kmlmp.js.map +npm notice 285B build/src/util/sysexits.d.ts +npm notice 186B build/src/util/sysexits.d.ts.map +npm notice 51B build/src/util/sysexits.js +npm notice 128B build/src/util/sysexits.js.map +npm notice 29.8kB build/tsconfig.tsbuildinfo +npm notice 5.4kB bundle.inc.sh +npm notice 1.7kB package.json +npm notice 2.8kB src/kmc.ts +npm notice 1.4kB src/kmlmc.ts +npm notice 3.2kB src/kmlmi.ts +npm notice 1.6kB src/kmlmp.ts +npm notice 237B src/util/sysexits.ts +npm notice 740B tsconfig.json +npm notice 449B tsconfig.kmc-base.json ``` For every release of **Keyman Developer**, the CI publishes a release of -`@keymanapp/lexical-model-compiler`. The version number is locked with -the particular version of Keyman Developer. +`@keymanapp/kmc`. The version number is locked with the particular version of +Keyman Developer. ### `set_npm_version` -This helper function sets the version in the `package.json` for the -given package, according to the VERSION_WITH_TAG variable. +This helper function sets the version in the `package.json` for the given +package, according to the VERSION_WITH_TAG variable. ```bash . $KEYMAN_ROOT/resources/shellHelperFunctions.sh diff --git a/docs/settings/linux/tasks.json b/docs/settings/linux/tasks.json index 526e872863d..e7f26215a63 100644 --- a/docs/settings/linux/tasks.json +++ b/docs/settings/linux/tasks.json @@ -6,6 +6,8 @@ "label": "core: build", "command": "${workspaceFolder}/core/build.sh", "args": [ + "configure:arch", + "build:arch", "--debug" ], "options": { @@ -117,7 +119,7 @@ "command": "${workspaceFolder}/core/build.sh", "args": [ "--debug", - "tests" + "test:arch" ], "problemMatcher": [ "$gcc" diff --git a/linux/debian/rules b/linux/debian/rules index 9f4848c8a80..e67ea1b5af5 100755 --- a/linux/debian/rules +++ b/linux/debian/rules @@ -23,7 +23,7 @@ override_dh_auto_configure: # keymankeyboardprocessor - we need to configure and build core before # we can configure ibus-keyman! cd core && \ - ./build.sh configure build -- --wrap-mode=nodownload --prefix=/usr --sysconfdir=/etc \ + ./build.sh --no-tests configure:arch build:arch -- --wrap-mode=nodownload --prefix=/usr --sysconfdir=/etc \ --localstatedir=/var --libdir=lib/$(DEB_TARGET_MULTIARCH) \ --libexecdir=lib/$(DEB_TARGET_MULTIARCH) # ibus-keyman @@ -35,7 +35,7 @@ override_dh_auto_configure: override_dh_auto_build: # keymankeyboardprocessor cd core && \ - ./build.sh build + ./build.sh --no-tests build:arch # ibus-keyman cd linux/ibus-keyman && \ ./build.sh build @@ -50,7 +50,7 @@ override_dh_auto_test: ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) # keymankeyboardprocessor cd core && \ - ./build.sh tests + ./build.sh --no-tests test:arch # ibus-keyman cd linux/ibus-keyman && \ ./build.sh test @@ -60,9 +60,8 @@ endif override_dh_auto_install: install -d $(CURDIR)/debian/tmp - # keymankeyboardprocessor cd core && \ - DESTDIR=$(CURDIR)/debian/tmp ./build.sh install + DESTDIR=$(CURDIR)/debian/tmp ./build.sh --no-tests install:arch # ibus-keyman cd linux/ibus-keyman && \ DESTDIR=$(CURDIR)/debian/tmp ./build.sh install diff --git a/linux/ibus-keyman/src/engine.c b/linux/ibus-keyman/src/engine.c index 6273d3ecd93..3a9c4aeabd1 100644 --- a/linux/ibus-keyman/src/engine.c +++ b/linux/ibus-keyman/src/engine.c @@ -976,7 +976,7 @@ ibus_keyman_engine_process_key_event( km_kbp_context *context = km_kbp_state_context(keyman->state); g_free(get_current_context_text(context)); g_message("DAR: %s - km_mod_state=0x%x", __FUNCTION__, km_mod_state); - km_kbp_process_event(keyman->state, keycode_to_vk[keycode], km_mod_state, isKeyDown); + km_kbp_process_event(keyman->state, keycode_to_vk[keycode], km_mod_state, isKeyDown, KM_KBP_EVENT_FLAG_DEFAULT); context = km_kbp_state_context(keyman->state); g_message("%s: after process key event", __FUNCTION__); g_free(get_current_context_text(context)); diff --git a/linux/ibus-keyman/tests/meson.build b/linux/ibus-keyman/tests/meson.build index 1e5db195731..8b2dea17a0e 100644 --- a/linux/ibus-keyman/tests/meson.build +++ b/linux/ibus-keyman/tests/meson.build @@ -7,7 +7,7 @@ test_files = [ ] kmx_dir = [ - core_dir / 'build/arch' / get_option('buildtype') / 'tests/unit/kmx' + core_dir / 'build/arch' / get_option('buildtype') / 'tests/kmx_test_source' ] kmnkbp_tests_lib = cc.find_library( @@ -24,7 +24,7 @@ test_env = [ test_include_dirs = [ core_include_dirs, - include_directories('../../../core/tests/unit/kmx'), + include_directories('../../../core/tests/kmx_test_source'), include_directories('../src'), include_directories('..'), ] diff --git a/linux/ibus-keyman/tests/scripts/run-tests.sh b/linux/ibus-keyman/tests/scripts/run-tests.sh index b80dd1a5f4e..b3673b3b95d 100755 --- a/linux/ibus-keyman/tests/scripts/run-tests.sh +++ b/linux/ibus-keyman/tests/scripts/run-tests.sh @@ -71,12 +71,16 @@ function run_tests() { exit 2 fi + # TODO: we are borrowing the KMX tests out of core; these should be shared in common + # to avoid deep links like this in future + KMX_TEST_DIR="${TOP_SRCDIR}"/../../core/tests/unit/kmx + if [ ! -d "$TESTDIR" ] || ! [[ $(find "${TESTDIR}/" -name \*.kmx 2>/dev/null | wc -l) -gt 0 ]]; then - if [[ $(find "${COMMON_ARCH_DIR}/tests/unit/kmx/" -name \*.kmx 2>/dev/null | wc -l) -gt 0 ]]; then + if [[ $(find "${KMX_TEST_DIR}/" -name \*.kmx 2>/dev/null | wc -l) -gt 0 ]]; then mkdir -p "$(realpath --canonicalize-missing "$TESTDIR"/..)" - ln -sf "$(realpath "${COMMON_ARCH_DIR}"/tests/unit/kmx)" "$TESTDIR" + ln -sf "$(realpath "${KMX_TEST_DIR}")" "$TESTDIR" else - echo "Can't find kmx files in ${COMMON_ARCH_DIR}/tests/unit/kmx" + echo "Can't find kmx files in ${KMX_TEST_DIR}" exit 3 fi fi diff --git a/linux/ibus-keyman/tests/scripts/setup-tests.sh b/linux/ibus-keyman/tests/scripts/setup-tests.sh index 7b466c96ac1..01801197780 100755 --- a/linux/ibus-keyman/tests/scripts/setup-tests.sh +++ b/linux/ibus-keyman/tests/scripts/setup-tests.sh @@ -33,12 +33,16 @@ else exit 2 fi -if [ ! -d "$TESTDIR" ] || ! [[ $(find "${TESTDIR}/" -name k_\*.kmx 2>/dev/null | wc -l) -gt 0 ]]; then - if [[ $(find "${COMMON_ARCH_DIR}/tests/unit/kmx/" -name k_\*.kmx 2>/dev/null | wc -l) -gt 0 ]]; then +# TODO: we are borrowing the KMX tests out of core; these should be shared in common +# to avoid deep links like this in future +KMX_TEST_DIR="${TOP_SRCDIR}"/../../core/tests/unit/kmx + +if [ ! -d "$TESTDIR" ] || ! [[ $(find "${TESTDIR}/" -name \*.kmx 2>/dev/null | wc -l) -gt 0 ]]; then + if [[ $(find "${KMX_TEST_DIR}/" -name \*.kmx 2>/dev/null | wc -l) -gt 0 ]]; then mkdir -p "$(realpath --canonicalize-missing "$TESTDIR"/..)" - ln -sf "$(realpath "${COMMON_ARCH_DIR}"/tests/unit/kmx)" "$TESTDIR" + ln -sf "$(realpath "${KMX_TEST_DIR}")" "$TESTDIR" else - echo "Can't find kmx files in ${COMMON_ARCH_DIR}/tests/unit/kmx" + echo "Can't find kmx files in ${KMX_TEST_DIR}" exit 3 fi fi diff --git a/linux/keyman-config/build-help.sh b/linux/keyman-config/build-help.sh index a326fcfffc3..97f06ca3d00 100755 --- a/linux/keyman-config/build-help.sh +++ b/linux/keyman-config/build-help.sh @@ -5,7 +5,7 @@ set -u ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary -THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" ## END STANDARD BUILD SCRIPT INCLUDE THIS_DIR=$(dirname "$THIS_SCRIPT") diff --git a/linux/scripts/build.sh b/linux/scripts/build.sh index 7aa9dab9e8a..a73734e8817 100755 --- a/linux/scripts/build.sh +++ b/linux/scripts/build.sh @@ -20,7 +20,7 @@ fi if [[ "${BUILDONLY}" == "no" ]]; then cd "$BASEDIR" - ../core/build.sh -t keyboardprocessor configure + ../core/build.sh --no-tests --target-path "$BASEDIR/keyboardprocessor" configure:arch cd keyboardprocessor/arch/release echo "reconfiguring keyboardprocessor meson with prefix ${INSTALLDIR}" @@ -28,7 +28,7 @@ if [[ "${BUILDONLY}" == "no" ]]; then # We need to build core first before we can configure ibus-keyman! cd "$BASEDIR" - ../core/build.sh -t keyboardprocessor build + ../core/build.sh --target-path "$BASEDIR/keyboardprocessor" build:arch cd "$BASEDIR/ibus-keyman" ./build.sh clean configure -- --prefix="${INSTALLDIR}" @@ -40,7 +40,7 @@ if [[ "${CONFIGUREONLY}" == "no" ]]; then # May 2021: For now, running tests here as well. We could move this elsewhere # in the future if we want to split out the tests, but they run in a couple of seconds # at present. - ../core/build.sh -t keyboardprocessor build tests + ../core/build.sh --target-path "$BASEDIR/keyboardprocessor" build:arch test:arch echo "building ibus-keyman" cd "$BASEDIR/ibus-keyman" diff --git a/linux/scripts/deb-packaging.sh b/linux/scripts/deb-packaging.sh index 6a76d7b400d..62fd389199d 100755 --- a/linux/scripts/deb-packaging.sh +++ b/linux/scripts/deb-packaging.sh @@ -5,7 +5,7 @@ set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary -THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE diff --git a/linux/scripts/reconf.sh b/linux/scripts/reconf.sh index 05f3514db21..cd74571a4a1 100755 --- a/linux/scripts/reconf.sh +++ b/linux/scripts/reconf.sh @@ -15,7 +15,7 @@ echo "Found tier ${TIER}, version ${VERSION}" # We need to configure+build core before we can configure ibus-keyman cd ../core -./build.sh clean configure build +./build.sh --no-tests clean:arch configure:arch build:arch cd "$BASEDIR/ibus-keyman" ./build.sh clean configure diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h index 17483dabd25..97907c67c1f 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMBinaryFileFormat.h @@ -16,7 +16,7 @@ typedef uint32_t DWORD; struct COMP_KEYBOARD { DWORD dwIdentifier; // 0000 Keyman compiled keyboard id DWORD dwFileVersion; // 0004 Version of the file - Keyman 4.0 is 0x0400 - DWORD dwCheckSum; // 0008 As stored in keyboard + DWORD dwCheckSum; // 0008 As stored in keyboard. DEPRECATED as of 16.0 DWORD KeyboardID; // 000C as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts DWORD IsRegistered; // 0010 DWORD version; // 0014 keyboard version @@ -44,8 +44,9 @@ struct COMP_KEYBOARD { #define VERSION_100 0x00000A00 #define VERSION_140 0x00000E00 #define VERSION_150 0x00000F00 +#define VERSION_160 0x00001000 #define VERSION_MIN VERSION_50 -#define VERSION_MAX VERSION_150 +#define VERSION_MAX VERSION_160 struct COMP_GROUP { DWORD dpName; // string (only debug) diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.h b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.h index 01ba3945827..8eec8bb4e32 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.h +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.h @@ -23,7 +23,6 @@ extern NSString *const kKMVisualKeyboardKey; @property (assign, nonatomic, readonly) DWORD identifier; @property (assign, nonatomic, readonly) DWORD fileVersion; -@property (assign, nonatomic, readonly) DWORD checkSum; @property (assign, nonatomic, readonly) DWORD keyboardID; @property (assign, nonatomic, readonly) BOOL isRegistered; @property (assign, nonatomic, readonly) BOOL isMnemonic; diff --git a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.m b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.m index ddc36a848d4..a2c705519a7 100644 --- a/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.m +++ b/mac/KeymanEngine4Mac/KeymanEngine4Mac/KME/KMXFile.m @@ -27,12 +27,8 @@ - (id)initWithFilePath:(NSString *)path { _filePath = nil; return nil; } - - if (![[self class] verifyCheckSum:path]) { - NSLog(@"errBadChecksum"); - return nil; - } - + + NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path]; if (file == nil) { //NSLog(@"Failed to open kmx file"); @@ -42,21 +38,20 @@ - (id)initWithFilePath:(NSString *)path { else { _filePath = [NSString stringWithString:path]; } - + struct COMP_KEYBOARD cmp_kb; [file seekToFileOffset:0]; size_t size = sizeof(cmp_kb); NSData *dataBuffer = [file readDataOfLength:size]; [dataBuffer getBytes:&cmp_kb length:size]; - + if (cmp_kb.dwFileVersion < VERSION_MIN || cmp_kb.dwFileVersion > VERSION_MAX) { [file closeFile]; return nil; } - + _identifier = cmp_kb.dwIdentifier; _fileVersion = cmp_kb.dwFileVersion; - _checkSum = cmp_kb.dwCheckSum; _keyboardID = cmp_kb.KeyboardID; _isRegistered = (cmp_kb.IsRegistered == 0?NO:YES); _version = cmp_kb.version; @@ -66,7 +61,7 @@ - (id)initWithFilePath:(NSString *)path { _startGroup = [[NSArray alloc] initWithArray:mStartGroup]; _flags = cmp_kb.dwFlags; _hotKey = cmp_kb.dwHotKey; - + struct COMP_STORE cmp_str[cmp_kb.cxStoreArray]; [file seekToFileOffset:cmp_kb.dpStoreArray]; size = sizeof(cmp_str); @@ -77,7 +72,7 @@ - (id)initWithFilePath:(NSString *)path { KMCompStore *kmStore = [[KMCompStore alloc] init]; kmStore.dwSystemID = cmp_str[i].dwSystemID; kmStore.name = [KMXFile UTF16StringWithPointer:cmp_str[i].dpName inFile:file]; - + if (kmStore.dwSystemID == TSS_VERSION) kmStore.string = [[KMXFile UTF16StringWithPointer:cmp_str[i].dpString inFile:file] stringByReplacingOccurrencesOfString:@"\n" withString:@""]; else { @@ -85,24 +80,24 @@ - (id)initWithFilePath:(NSString *)path { if (kmStore.dwSystemID == TSS_MNEMONIC && [kmStore.string isEqualToString:@"1"]) _isMnemonic = YES; } - + if (kmStore.dwSystemID == TSS_VERSION) kmStore.string = [[KMXFile UTF16StringWithPointer:cmp_str[i].dpString inFile:file] stringByReplacingOccurrencesOfString:@"\n" withString:@""]; else kmStore.string = [KMXFile UTF16StringWithPointer:cmp_str[i].dpString inFile:file]; - + [mStore addObject:kmStore]; } - + _store = [[NSArray alloc] initWithArray:mStore]; _storeSaved = [[NSArray alloc] initWithArray:mStore copyItems:YES]; - + struct COMP_GROUP cmp_grp[cmp_kb.cxGroupArray]; [file seekToFileOffset:cmp_kb.dpGroupArray]; size = sizeof(cmp_grp); dataBuffer = [file readDataOfLength:size]; [dataBuffer getBytes:cmp_grp length:size]; - + NSMutableArray *mGroup = [[NSMutableArray alloc] initWithCapacity:cmp_kb.cxGroupArray]; for (int i = 0; i < cmp_kb.cxGroupArray; i++) { KMCompGroup *kmGrp = [[KMCompGroup alloc] init]; @@ -110,13 +105,13 @@ - (id)initWithFilePath:(NSString *)path { kmGrp.match = [KMXFile UTF16StringWithPointer:cmp_grp[i].dpMatch inFile:file]; kmGrp.noMatch = [KMXFile UTF16StringWithPointer:cmp_grp[i].dpNoMatch inFile:file]; kmGrp.fUsingKeys = cmp_grp[i].fUsingKeys; - + struct COMP_KEY cmp_keys[cmp_grp[i].cxKeyArray]; [file seekToFileOffset:cmp_grp[i].dpKeyArray]; size = sizeof(cmp_keys); dataBuffer = [file readDataOfLength:size]; [dataBuffer getBytes:cmp_keys length:size]; - + for (int j = 0; j < cmp_grp[i].cxKeyArray; j++) { KMCompKey *kmKey = [[KMCompKey alloc] init]; kmKey.key = cmp_keys[j].Key; @@ -126,19 +121,19 @@ - (id)initWithFilePath:(NSString *)path { kmKey.context = [KMXFile UTF16StringWithPointer:cmp_keys[j].dpContext inFile:file]; [kmGrp.keys addObject:kmKey]; } - + [mGroup addObject:kmGrp]; } _group = [[NSArray alloc] initWithArray:mGroup]; - + [file seekToFileOffset:cmp_kb.dpBitmapOffset]; NSData *bitmapData = [file readDataOfLength:cmp_kb.dwBitmapSize]; NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:bitmapData]; _bitmap = [[NSImage alloc] initWithCGImage:imageRep.CGImage size:NSMakeSize(CGImageGetWidth(imageRep.CGImage), CGImageGetHeight(imageRep.CGImage))]; - + [file closeFile]; } - + return self; } @@ -149,50 +144,46 @@ - (NSString *)description { return str; } -- (BOOL)isValid { +- (BOOL)isValid { for (KMCompGroup *gp in self.group) { if ((gp.match && ![gp.match isValidCode]) || (gp.noMatch && ![gp.noMatch isValidCode])) return NO; - + for (KMCompKey *kmKey in gp.keys) { if (![kmKey.context isValidCode] || ![kmKey.output isValidCode]) return NO; } } - + return YES; } + (NSDictionary *)keyboardInfoFromKmxFile:(NSString *)path { NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path]; - + if (file == nil) { //NSLog(@"Failed to open file"); return nil; } - - if (![[self class] verifyCheckSum:path]) { - NSLog(@"errBadChecksum"); - return nil; - } - + + struct COMP_KEYBOARD cmp_kb; [file seekToFileOffset:0]; size_t size = sizeof(cmp_kb); NSData *dataBuffer = [file readDataOfLength:size]; [dataBuffer getBytes:&cmp_kb length:size]; - + if (cmp_kb.dwFileVersion < VERSION_MIN || cmp_kb.dwFileVersion > VERSION_MAX) { [file closeFile]; return nil; } - + struct COMP_STORE cmp_str[cmp_kb.cxStoreArray]; [file seekToFileOffset:cmp_kb.dpStoreArray]; size = sizeof(cmp_str); dataBuffer = [file readDataOfLength:size]; [dataBuffer getBytes:cmp_str length:size]; - + NSString *nameStr = nil; NSString *verStr = nil; NSString *copyrightStr = nil; @@ -212,9 +203,9 @@ + (NSDictionary *)keyboardInfoFromKmxFile:(NSString *)path { NSData *bitmapData = [file readDataOfLength:cmp_kb.dwBitmapSize]; NSBitmapImageRep *imageRep = [NSBitmapImageRep imageRepWithData:bitmapData]; NSImage *icon = [[NSImage alloc] initWithCGImage:imageRep.CGImage size:NSMakeSize(CGImageGetWidth(imageRep.CGImage), CGImageGetHeight(imageRep.CGImage))]; - + [file closeFile]; - + if (!nameStr || !nameStr.length) { nameStr = [path.lastPathComponent stringByReplacingOccurrencesOfString:@".kmx" withString:@""]; } @@ -222,7 +213,7 @@ + (NSDictionary *)keyboardInfoFromKmxFile:(NSString *)path { verStr = @""; if (!copyrightStr) copyrightStr = @""; - + NSDictionary *info; if (icon) { info = [[NSDictionary alloc] initWithObjectsAndKeys:nameStr, kKMKeyboardNameKey, @@ -237,14 +228,14 @@ + (NSDictionary *)keyboardInfoFromKmxFile:(NSString *)path { copyrightStr, kKMKeyboardCopyrightKey, visualKeyboard, kKMVisualKeyboardKey, nil]; } - + return info; } + (NSString *)UTF16StringWithPointer:(DWORD)dp inFile:(NSFileHandle *)file { if (dp == 0) return nil; - + [file seekToFileOffset:dp]; UTF16Char ch = '\0'; size_t size = sizeof(ch); @@ -257,74 +248,9 @@ + (NSString *)UTF16StringWithPointer:(DWORD)dp inFile:(NSFileHandle *)file { dataBuffer = [file readDataOfLength:size]; [dataBuffer getBytes:&ch length:size]; } - - return mStr; -} - -#pragma mark - CRC32 - -+ (BOOL)verifyCheckSum:(NSString *)path { - NSFileHandle *file = [NSFileHandle fileHandleForReadingAtPath:path]; - if (file == nil) { - //NSLog(@"Failed to open file"); - return NO; - } - - [file seekToFileOffset:0]; - NSMutableData *dataBuffer = [[file readDataToEndOfFile] mutableCopy]; - [dataBuffer replaceBytesInRange:NSMakeRange(8, 4) withBytes:0 length:4]; // set dwCheckSum = 0 before calculating CRC32 - unsigned long crc = [self getCRC32:dataBuffer]; - - struct COMP_KEYBOARD cmp_kb; - [file seekToFileOffset:0]; - size_t size = sizeof(cmp_kb); - dataBuffer = [[file readDataOfLength:size] mutableCopy]; - [dataBuffer getBytes:&cmp_kb length:size]; - [file closeFile]; - - return (crc == cmp_kb.dwCheckSum); -} -#define CRC32_POLYNOMIAL 0xEDB88320L -unsigned long CRCTable[256]; - -+ (void)buildCRCTable { - static BOOL tableBuilt = FALSE; - int i; - int j; - unsigned long crc; - - if (!tableBuilt) { - for (i = 0; i <= 255; i++) { - crc = i; - - for (j = 8; j > 0; j--) - if(crc & 1) crc = (crc >> 1) ^ CRC32_POLYNOMIAL; else crc >>= 1; - - CRCTable[i] = crc; - } - } -} - -+ (unsigned long)calculateBufferCRC:(unsigned long)count pointer:(Byte *)p { - unsigned long temp1; - unsigned long temp2; - unsigned long crc = 0xFFFFFFFFL; - - [self buildCRCTable]; - - while (count-- != 0) { - temp1 = ( crc >> 8 ) & 0x00FFFFFFL; - temp2 = CRCTable[((int) crc ^ *p++) & 0xff]; - crc = temp1 ^ temp2; - } - - return crc; + return mStr; } -+ (unsigned long)getCRC32:(NSData *)data { - Byte *bytes = (Byte *)[data bytes]; - return [self calculateBufferCRC:[data length] pointer:bytes]; -} @end diff --git a/package-lock.json b/package-lock.json index b3b609276d1..216639c4be9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "root", - "lockfileVersion": 2, + "lockfileVersion": 3, "requires": true, "packages": { "": { @@ -8,99 +8,31 @@ "workspaces": [ "resources/gosh", "resources/build/version", - "developer/src/kmlmc", + "core/include/ldml", + "developer/src/kmc-keyboard", + "developer/src/kmc-model", + "developer/src/kmc-model-info", + "developer/src/kmc-package", + "developer/src/kmc", "developer/src/server", "common/models/*", "common/tools/*", "common/web/*", "common/predictive-text", + "common/tools/hextobin", "web" ], - "devDependencies": { - "chai": "^4.3.4", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - } - }, - "common/core/web/input-processor": { - "name": "@keymanapp/input-processor", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@keymanapp/keyboard-processor": "*", - "@keymanapp/keyman-version": "*", - "@keymanapp/lexical-model-layer": "*", - "@keymanapp/models-types": "*", - "@keymanapp/web-utils": "*", - "eventemitter3": "^4.0.0" - }, - "devDependencies": { - "@keymanapp/lexical-model-compiler": "*", - "@keymanapp/resources-gosh": "*", - "@types/node": "^11.9.4", - "chai": "^4.3.4", - "mocha": "^8.4.0", - "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" - } - }, - "common/core/web/keyboard-processor": { - "name": "@keymanapp/keyboard-processor", - "extraneous": true, - "license": "MIT", "dependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-types": "*", - "@keymanapp/web-utils": "*", - "@types/node": "^11.9.4" + "@keymanapp/common-types": "file:common/web/types", + "@keymanapp/hextobin": "file:common/tools/hextobin", + "@keymanapp/keyman-version": "file:common/web/keyman-version", + "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" }, "devDependencies": { - "@keymanapp/resources-gosh": "*", "chai": "^4.3.4", - "mocha": "^8.4.0", + "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^9.1.1", - "typescript": "^4.9.5" - } - }, - "common/core/web/tools/recorder": { - "name": "@keymanapp/recorder-core", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@keymanapp/keyboard-processor": "*", - "@keymanapp/keyman-version": "*", - "@keymanapp/models-types": "*", - "@keymanapp/web-utils": "*", - "@types/node": "^11.9.4" - }, - "devDependencies": { - "typescript": "^4.9.5" - } - }, - "common/core/web/tools/sentry-manager": { - "name": "@keymanapp/web-sentry-manager", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@keymanapp/keyman-version": "*", - "@sentry/browser": "^5.27.4" - }, - "devDependencies": { - "typescript": "^4.9.5" - } - }, - "common/core/web/utils": { - "name": "@keymanapp/web-utils", - "extraneous": true, - "license": "MIT", - "devDependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/resources-gosh": "*", - "@types/node": "^14.0.5", + "ts-node": "^10.9.1", "typescript": "^4.9.5" } }, @@ -123,9 +55,8 @@ }, "common/models/templates/node_modules/@types/node": { "version": "14.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", - "dev": true + "dev": true, + "license": "MIT" }, "common/models/types": { "name": "@keymanapp/models-types", @@ -152,8 +83,7 @@ }, "common/models/wordbreakers/node_modules/@types/node": { "version": "14.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==" + "license": "MIT" }, "common/predictive-text": { "name": "@keymanapp/lexical-model-layer", @@ -194,9 +124,33 @@ }, "common/predictive-text/node_modules/@types/node": { "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true + "dev": true, + "license": "MIT" + }, + "common/tools/hextobin": { + "name": "@keymanapp/hextobin", + "license": "MIT", + "dependencies": { + "commander": "^5.1.0" + }, + "bin": { + "hextobin": "build/hextobin.js" + }, + "devDependencies": { + "@types/node": "^18.7.18" + } + }, + "common/tools/hextobin/node_modules/@types/node": { + "version": "18.11.15", + "dev": true, + "license": "MIT" + }, + "common/tools/hextobin/node_modules/commander": { + "version": "5.1.0", + "license": "MIT", + "engines": { + "node": ">= 6" + } }, "common/tools/sourcemap-path-remapper": { "name": "@keymanapp/sourcemap-path-remapper", @@ -217,18 +171,12 @@ }, "common/tools/sourcemap-path-remapper/node_modules/@types/node": { "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true + "dev": true, + "license": "MIT" }, - "common/web/es6-shim": { - "name": "@keymanapp/es6-shim", - "extraneous": true, - "license": "MIT", - "dependencies": { - "es6-shim": "^0.35.5" - }, - "devDependencies": {} + "common/tools/sourcemap-path-remapper/node_modules/convert-source-map": { + "version": "2.0.0", + "license": "MIT" }, "common/web/input-processor": { "name": "@keymanapp/input-processor", @@ -242,7 +190,7 @@ "eventemitter3": "^4.0.0" }, "devDependencies": { - "@keymanapp/lexical-model-compiler": "*", + "@keymanapp/kmc": "*", "@keymanapp/resources-gosh": "*", "@types/node": "^11.9.4", "chai": "^4.3.4", @@ -274,25 +222,14 @@ "name": "@keymanapp/keyman-version", "license": "MIT", "devDependencies": { + "@types/node": "^18.7.13", "typescript": "^4.9.5" } }, - "common/web/keyman-version-node": { - "name": "@keymanapp/keyman-version-node", - "extraneous": true, - "license": "MIT", - "devDependencies": { - "typescript": "^4.9.5" - } - }, - "common/web/keyman-version/node": { - "name": "@keymanapp/keyman-version-node", - "extraneous": true, - "license": "MIT", - "devDependencies": { - "@keymanapp/resources-gosh": "*", - "typescript": "^4.9.5" - } + "common/web/keyman-version/node_modules/@types/node": { + "version": "18.11.15", + "dev": true, + "license": "MIT" }, "common/web/lm-message-types": { "name": "@keymanapp/lm-message-types", @@ -341,9 +278,8 @@ }, "common/web/lm-worker/node_modules/@types/node": { "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true + "dev": true, + "license": "MIT" }, "common/web/recorder": { "name": "@keymanapp/recorder-core", @@ -370,138 +306,61 @@ "typescript": "^4.9.5" } }, - "common/web/utils": { - "name": "@keymanapp/web-utils", - "license": "MIT", - "devDependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/resources-gosh": "*", - "@types/node": "^14.0.5", - "typescript": "^4.9.5" - } - }, - "common/web/utils/node_modules/@types/node": { - "version": "14.18.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.18.tgz", - "integrity": "sha512-B9EoJFjhqcQ9OmQrNorItO+OwEOORNn3S31WuiHvZY/dm9ajkB7AKD/8toessEtHHNL+58jofbq7hMMY9v4yig==", - "dev": true - }, - "developer/js": { - "name": "@keymanapp/lexical-model-compiler", - "extraneous": true, + "common/web/types": { + "name": "@keymanapp/common-types", "license": "MIT", "dependencies": { - "@keymanapp/models-types": "*", - "commander": "^3.0.0", - "typescript": "^4.9.5", - "xml2js": "^0.4.19" - }, - "bin": { - "kmlmc": "dist/kmlmc.js", - "kmlmi": "dist/kmlmi.js", - "kmlmp": "dist/kmlmp.js" + "@keymanapp/keyman-version": "*", + "ajv": "^8.11.0", + "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", + "semver": "^7.3.7", + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" }, "devDependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-templates": "*", - "@keymanapp/models-wordbreakers": "*", "@types/chai": "^4.1.7", + "@types/git-diff": "^2.0.3", "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", + "@types/semver": "^7.3.12", "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", "chai": "^4.3.4", "chalk": "^2.4.2", - "jszip": "^3.7.0", + "git-diff": "^2.0.6", + "hexy": "^0.3.4", "mocha": "^8.4.0", - "ts-node": "^9.1.1" - }, - "engines": { - "node": ">=16.0.0" + "ts-node": "^9.1.1", + "typescript": "4.6.3" } }, - "developer/server": { - "name": "@keymanapp/developer-server", - "extraneous": true, - "license": "MIT", - "dependencies": { - "@sentry/node": "^6.16.1", - "chalk": "^4.1.2", - "express": "^4.17.2", - "multer": "^1.4.4", - "ngrok": "^4.2.2", - "open": "^8.4.0", - "ws": "^8.3.0" - }, - "devDependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/resources-gosh": "*", - "@types/chai": "^4.3.0", - "@types/express": "^4.17.13", - "@types/mocha": "^9.1.0", - "@types/multer": "^1.4.7", - "@types/node": "^17.0.0", - "@types/ws": "^8.2.2", - "chai": "^4.3.4", - "copyfiles": "^2.4.1", - "mocha": "^9.1.4", - "ts-node": "^10.4.0", - "tsc-watch": "^4.5.0", - "typescript": "^4.9.5" - }, - "optionalDependencies": { - "hetrodo-node-hide-console-window-napi": "git+ssh://git@github.com/keymanapp/hetrodo-node-hide-console-window-napi.git#858b23036a9963b40ad6ff3c5bacd421e5839b92", - "node-windows-trayicon": "git+ssh://git@github.com/keymanapp/node-windows-trayicon.git#1e46786082213f3edcddd5953e33f5abdc7ea05f" - } + "common/web/types/node_modules/@types/mocha": { + "version": "5.2.7", + "dev": true, + "license": "MIT" }, - "developer/src/kmlmc": { - "name": "@keymanapp/lexical-model-compiler", + "common/web/types/node_modules/@types/node": { + "version": "10.17.60", + "dev": true, + "license": "MIT" + }, + "common/web/types/node_modules/ajv": { + "version": "8.11.2", "license": "MIT", "dependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-types": "*", - "commander": "^3.0.0", - "typescript": "^4.9.5", - "xml2js": "^0.4.19" - }, - "bin": { - "kmlmc": "dist/kmlmc.js", - "kmlmi": "dist/kmlmi.js", - "kmlmp": "dist/kmlmp.js" - }, - "devDependencies": { - "@keymanapp/models-templates": "*", - "@keymanapp/models-wordbreakers": "*", - "@types/chai": "^4.1.7", - "@types/mocha": "^5.2.7", - "@types/node": "^10.14.6", - "@types/xml2js": "^0.4.5", - "chai": "^4.3.4", - "chalk": "^2.4.2", - "jszip": "^3.7.0", - "mocha": "^10.0.0", - "ts-node": "^10.9.1" + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" }, - "engines": { - "node": ">=16.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "developer/src/kmlmc/node_modules/@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "developer/src/kmlmc/node_modules/@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - }, - "developer/src/kmlmc/node_modules/ansi-styles": { + "common/web/types/node_modules/ansi-styles": { "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -509,11 +368,10 @@ "node": ">=4" } }, - "developer/src/kmlmc/node_modules/chalk": { + "common/web/types/node_modules/chalk": { "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -523,2979 +381,2985 @@ "node": ">=4" } }, - "developer/src/kmlmc/node_modules/color-convert": { + "common/web/types/node_modules/color-convert": { "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } }, - "developer/src/kmlmc/node_modules/color-name": { + "common/web/types/node_modules/color-name": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true + "dev": true, + "license": "MIT" }, - "developer/src/kmlmc/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "common/web/types/node_modules/js-yaml": { + "version": "4.0.0", "dev": true, - "engines": { - "node": ">=4" + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "developer/src/kmlmc/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "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, + "license": "ISC", "dependencies": { - "has-flag": "^3.0.0" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=4" + "node": "*" } }, - "developer/src/server": { - "name": "@keymanapp/developer-server", + "common/web/types/node_modules/mocha": { + "version": "8.4.0", + "dev": true, "license": "MIT", "dependencies": { - "@sentry/node": "^6.16.1", - "chalk": "^4.1.2", - "express": "^4.17.2", - "multer": "^1.4.5-lts.1", - "ngrok": "^4.2.2", - "open": "^8.4.0", - "ws": "^8.3.0" + "@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" }, - "devDependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/resources-gosh": "*", - "@types/chai": "^4.3.0", - "@types/express": "^4.17.13", - "@types/mocha": "^9.1.0", - "@types/multer": "^1.4.7", - "@types/node": "^17.0.0", - "@types/ws": "^8.2.2", - "chai": "^4.3.4", - "copyfiles": "^2.4.1", - "mocha": "^10.0.0", - "ts-node": "^10.9.1", - "tsc-watch": "^4.5.0", - "typescript": "^4.9.5" + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" }, - "optionalDependencies": { - "hetrodo-node-hide-console-window-napi": "keymanapp/hetrodo-node-hide-console-window-napi#keyman-15.0", - "node-windows-trayicon": "keymanapp/node-windows-trayicon#keyman-16.0" + "engines": { + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, - "developer/src/server/node_modules/@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, - "developer/src/server/node_modules/@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "dev": true + "common/web/types/node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "developer/src/server/node_modules/busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "common/web/types/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "streamsearch": "^1.1.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=10.16.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "developer/src/server/node_modules/multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", + "common/web/types/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "common/web/types/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", "dependencies": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" + "has-flag": "^3.0.0" }, "engines": { - "node": ">= 6.0.0" + "node": ">=4" } }, - "developer/src/server/node_modules/streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "common/web/types/node_modules/supports-color/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=4" } }, - "node_modules/@actions/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz", - "integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==", + "common/web/types/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": { - "@actions/http-client": "^2.0.1", - "uuid": "^8.3.2" + "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" } }, - "node_modules/@actions/github": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@actions/github/-/github-2.2.0.tgz", - "integrity": "sha512-9UAZqn8ywdR70n3GwVle4N8ALosQs4z50N7XMXrSTUVOmVpaBC5kE3TRTT7qQdi3OaQV24mjGuJZsHUmhD+ZXw==", - "dependencies": { - "@actions/http-client": "^1.0.3", - "@octokit/graphql": "^4.3.1", - "@octokit/rest": "^16.43.1" + "common/web/types/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" } }, - "node_modules/@actions/github/node_modules/@actions/http-client": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", - "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", + "common/web/types/node_modules/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "common/web/utils": { + "name": "@keymanapp/web-utils", + "license": "MIT", + "devDependencies": { + "@keymanapp/keyman-version": "*", + "@keymanapp/resources-gosh": "*", + "@types/node": "^14.0.5", + "typescript": "^4.9.5" + } + }, + "common/web/utils/node_modules/@types/node": { + "version": "14.18.18", + "dev": true, + "license": "MIT" + }, + "core/include/ldml": { + "name": "@keymanapp/ldml-keyboard-constants", + "license": "MIT" + }, + "developer/src/kmc": { + "name": "@keymanapp/kmc", + "license": "MIT", "dependencies": { - "tunnel": "0.0.6" + "@keymanapp/common-types": "*", + "@keymanapp/keyman-version": "*", + "@keymanapp/kmc-keyboard": "*", + "@keymanapp/kmc-model": "*", + "@keymanapp/kmc-model-info": "*", + "@keymanapp/kmc-package": "*", + "@keymanapp/models-types": "*", + "commander": "^10.0.0" + }, + "bin": { + "kmc": "build/src/kmc.js", + "kmlmc": "build/src/kmlmc.js", + "kmlmi": "build/src/kmlmi.js", + "kmlmp": "build/src/kmlmp.js" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "esbuild": "^0.15.8", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "^4.5.4" } }, - "node_modules/@actions/http-client": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", - "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", + "developer/src/kmc-keyboard": { + "name": "@keymanapp/kmc-keyboard", + "license": "MIT", "dependencies": { - "tunnel": "^0.0.6" + "@keymanapp/keyman-version": "*", + "ajv": "^8.11.0", + "restructure": "git+https://github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", + "semver": "^7.3.7", + "xml2js": "git+https://github.com/keymanapp/dependency-node-xml2js#535fe732dc408d697e0f847c944cc45f0baf0829" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "@types/semver": "^7.3.12", + "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "4.6.3" } }, - "node_modules/@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", + "developer/src/kmc-keyboard/node_modules/@types/mocha": { + "version": "5.2.7", "dev": true, - "engines": { - "node": ">=0.1.90" + "license": "MIT" + }, + "developer/src/kmc-keyboard/node_modules/@types/node": { + "version": "10.17.60", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-keyboard/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" } }, - "node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", + "developer/src/kmc-keyboard/node_modules/ansi-styles": { + "version": "3.2.1", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=12" + "node": ">=4" } }, - "node_modules/@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", + "developer/src/kmc-keyboard/node_modules/chalk": { + "version": "2.4.2", "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, "engines": { - "node": ">=6.0.0" + "node": ">=4" } }, - "node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true - }, - "node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", + "developer/src/kmc-keyboard/node_modules/color-convert": { + "version": "1.9.3", "dev": true, + "license": "MIT", "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" + "color-name": "1.1.3" } }, - "node_modules/@keymanapp/auto-history-action": { - "resolved": "resources/build/version", - "link": true - }, - "node_modules/@keymanapp/developer-server": { - "resolved": "developer/src/server", - "link": true - }, - "node_modules/@keymanapp/input-processor": { - "resolved": "common/web/input-processor", - "link": true - }, - "node_modules/@keymanapp/keyboard-processor": { - "resolved": "common/web/keyboard-processor", - "link": true - }, - "node_modules/@keymanapp/keyman-version": { - "resolved": "common/web/keyman-version", - "link": true - }, - "node_modules/@keymanapp/lexical-model-compiler": { - "resolved": "developer/src/kmlmc", - "link": true + "developer/src/kmc-keyboard/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" }, - "node_modules/@keymanapp/lexical-model-layer": { - "resolved": "common/predictive-text", - "link": true + "developer/src/kmc-keyboard/node_modules/js-yaml": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } }, - "node_modules/@keymanapp/lm-message-types": { - "resolved": "common/web/lm-message-types", - "link": true + "developer/src/kmc-keyboard/node_modules/json-schema-traverse": { + "version": "1.0.0", + "license": "MIT" }, - "node_modules/@keymanapp/lm-worker": { - "resolved": "common/web/lm-worker", - "link": true + "developer/src/kmc-keyboard/node_modules/minimatch": { + "version": "3.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } }, - "node_modules/@keymanapp/models-templates": { - "resolved": "common/models/templates", - "link": true - }, - "node_modules/@keymanapp/models-types": { - "resolved": "common/models/types", - "link": true - }, - "node_modules/@keymanapp/models-wordbreakers": { - "resolved": "common/models/wordbreakers", - "link": true - }, - "node_modules/@keymanapp/recorder-core": { - "resolved": "common/web/recorder", - "link": true - }, - "node_modules/@keymanapp/resources-gosh": { - "resolved": "resources/gosh", - "link": true + "developer/src/kmc-keyboard/node_modules/mocha": { + "version": "8.4.0", + "dev": true, + "license": "MIT", + "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" + } }, - "node_modules/@keymanapp/sourcemap-path-remapper": { - "resolved": "common/tools/sourcemap-path-remapper", - "link": true + "developer/src/kmc-keyboard/node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@keymanapp/web-sentry-manager": { - "resolved": "common/web/sentry-manager", - "link": true + "developer/src/kmc-keyboard/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } }, - "node_modules/@keymanapp/web-utils": { - "resolved": "common/web/utils", - "link": true + "developer/src/kmc-keyboard/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" }, - "node_modules/@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", + "developer/src/kmc-keyboard/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^6.0.3" + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "peer": true, - "dependencies": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" + "developer/src/kmc-keyboard/node_modules/supports-color/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", + "developer/src/kmc-keyboard/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": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" + "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" } }, - "node_modules/@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "dependencies": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" + "developer/src/kmc-keyboard/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" } }, - "node_modules/@octokit/openapi-types": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", - "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" + "developer/src/kmc-keyboard/node_modules/typescript": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.6.3.tgz", + "integrity": "sha512-yNIatDa5iaofVozS/uQJEl3JRWLKKGJKh6Yaiv0GLGSuhpFJe7P3SbHZ8/yjAHRQwKRoA6YZqlfjXWmVzoVSMw==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } }, - "node_modules/@octokit/plugin-paginate-rest": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz", - "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==", + "developer/src/kmc-model": { + "name": "@keymanapp/kmc-model", + "license": "MIT", "dependencies": { - "@octokit/types": "^2.0.1" + "@keymanapp/keyman-version": "*", + "@keymanapp/models-types": "*", + "commander": "^3.0.0", + "typescript": "^4.9.5", + "xml2js": "^0.4.19" + }, + "devDependencies": { + "@keymanapp/models-templates": "*", + "@keymanapp/models-wordbreakers": "*", + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "esbuild": "^0.15.7", + "mocha": "^10.0.0", + "ts-node": "^10.9.1" } }, - "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "developer/src/kmc-model-info": { + "name": "@keymanapp/kmc-model-info", + "license": "MIT", "dependencies": { - "@types/node": ">= 8" + "@keymanapp/keyman-version": "*", + "@keymanapp/kmc-package": "*", + "@keymanapp/models-types": "*" + }, + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "@types/xml2js": "^0.4.5", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "^4.5.4" } }, - "node_modules/@octokit/plugin-request-log": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", - "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "peerDependencies": { - "@octokit/core": ">=3" + "developer/src/kmc-model-info/node_modules/@types/mocha": { + "version": "5.2.7", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-model-info/node_modules/@types/node": { + "version": "10.17.60", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-model-info/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz", - "integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==", + "developer/src/kmc-model-info/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", "dependencies": { - "@octokit/types": "^2.0.1", - "deprecation": "^2.3.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "developer/src/kmc-model-info/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": ">= 8" + "color-name": "1.1.3" } }, - "node_modules/@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", + "developer/src/kmc-model-info/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-model-info/node_modules/js-yaml": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", + "developer/src/kmc-model-info/node_modules/minimatch": { + "version": "3.0.4", + "dev": true, + "license": "ISC", "dependencies": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/@octokit/rest": { - "version": "16.43.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.2.tgz", - "integrity": "sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==", + "developer/src/kmc-model-info/node_modules/mocha": { + "version": "8.4.0", + "dev": true, + "license": "MIT", "dependencies": { - "@octokit/auth-token": "^2.4.0", - "@octokit/plugin-paginate-rest": "^1.1.1", - "@octokit/plugin-request-log": "^1.0.0", - "@octokit/plugin-rest-endpoint-methods": "2.4.0", - "@octokit/request": "^5.2.0", - "@octokit/request-error": "^1.0.2", - "atob-lite": "^2.0.0", - "before-after-hook": "^2.0.0", - "btoa-lite": "^1.0.0", - "deprecation": "^2.0.0", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "lodash.uniq": "^4.5.0", - "octokit-pagination-methods": "^1.1.0", - "once": "^1.4.0", - "universal-user-agent": "^4.0.0" + "@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" } }, - "node_modules/@octokit/rest/node_modules/@octokit/request-error": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", - "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", - "dependencies": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" + "developer/src/kmc-model-info/node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@octokit/rest/node_modules/@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", + "developer/src/kmc-model-info/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": ">= 8" + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@octokit/rest/node_modules/universal-user-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", - "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", - "dependencies": { - "os-name": "^3.1.0" - } + "developer/src/kmc-model-info/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" }, - "node_modules/@octokit/types": { - "version": "6.34.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", - "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", + "developer/src/kmc-model-info/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", "dependencies": { - "@octokit/openapi-types": "^11.2.0" + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@sentry/browser": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.30.0.tgz", - "integrity": "sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw==", - "dependencies": { - "@sentry/core": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - }, + "developer/src/kmc-model-info/node_modules/supports-color/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/@sentry/cli": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.2.0.tgz", - "integrity": "sha512-ywFtB8VHyWN248LuM67fsRtdMLif/SOHYY3zyef5WybvnAmRLDmGTWK//hSUCebsHBpehRIkmt4iMiyUXwgd5w==", + "developer/src/kmc-model-info/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, - "hasInstallScript": true, "dependencies": { - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.7", - "npmlog": "^6.0.1", - "progress": "^2.0.3", - "proxy-from-env": "^1.1.0", - "which": "^2.0.2" + "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": { - "sentry-cli": "bin/sentry-cli" + "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": ">= 12" + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" } }, - "node_modules/@sentry/cli/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "developer/src/kmc-model-info/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": ">=8" + "node": ">=0.3.1" } }, - "node_modules/@sentry/cli/node_modules/are-we-there-yet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", - "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", + "developer/src/kmc-model/node_modules/@types/mocha": { + "version": "5.2.7", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-model/node_modules/@types/node": { + "version": "10.17.60", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-model/node_modules/ansi-styles": { + "version": "3.2.1", "dev": true, + "license": "MIT", "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" + "color-convert": "^1.9.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16" + "node": ">=4" } }, - "node_modules/@sentry/cli/node_modules/gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "developer/src/kmc-model/node_modules/chalk": { + "version": "2.4.2", "dev": true, + "license": "MIT", "dependencies": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=4" + } + }, + "developer/src/kmc-model/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" } }, - "node_modules/@sentry/cli/node_modules/is-fullwidth-code-point": { + "developer/src/kmc-model/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-model/node_modules/has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/@sentry/cli/node_modules/npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "developer/src/kmc-model/node_modules/supports-color": { + "version": "5.5.0", "dev": true, + "license": "MIT", "dependencies": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" + "has-flag": "^3.0.0" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + "node": ">=4" } }, - "node_modules/@sentry/cli/node_modules/readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, + "developer/src/kmc-package": { + "name": "@keymanapp/kmc-package", + "license": "MIT", "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" + "jszip": "^3.7.0" }, - "engines": { - "node": ">= 6" + "devDependencies": { + "@types/chai": "^4.1.7", + "@types/mocha": "^5.2.7", + "@types/node": "^10.14.6", + "c8": "^7.12.0", + "chai": "^4.3.4", + "chalk": "^2.4.2", + "mocha": "^8.4.0", + "ts-node": "^9.1.1", + "typescript": "^4.5.4" } }, - "node_modules/@sentry/cli/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "developer/src/kmc-package/node_modules/@types/mocha": { + "version": "5.2.7", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-package/node_modules/@types/node": { + "version": "10.17.60", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-package/node_modules/ansi-styles": { + "version": "3.2.1", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/@sentry/cli/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "developer/src/kmc-package/node_modules/chalk": { + "version": "2.4.2", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/@sentry/cli/node_modules/wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "developer/src/kmc-package/node_modules/color-convert": { + "version": "1.9.3", "dev": true, + "license": "MIT", "dependencies": { - "string-width": "^1.0.2 || 2 || 3 || 4" + "color-name": "1.1.3" } }, - "node_modules/@sentry/core": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", - "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", + "developer/src/kmc-package/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-package/node_modules/js-yaml": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" + "argparse": "^2.0.1" }, - "engines": { - "node": ">=6" + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@sentry/hub": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", - "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", + "developer/src/kmc-package/node_modules/minimatch": { + "version": "3.0.4", + "dev": true, + "license": "ISC", "dependencies": { - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=6" + "node": "*" } }, - "node_modules/@sentry/minimal": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", - "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", + "developer/src/kmc-package/node_modules/mocha": { + "version": "8.4.0", + "dev": true, + "license": "MIT", "dependencies": { - "@sentry/hub": "5.30.0", - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" + "@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": ">=6" + "node": ">= 10.12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, - "node_modules/@sentry/node": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.19.6.tgz", - "integrity": "sha512-kHQMfsy40ZxxdS9zMPmXCOOLWOJbQj6/aVSHt/L1QthYcgkAi7NJQNXnQIPWQDe8eP3DfNIWM7dc446coqjXrQ==", - "dependencies": { - "@sentry/core": "6.19.6", - "@sentry/hub": "6.19.6", - "@sentry/types": "6.19.6", - "@sentry/utils": "6.19.6", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, + "developer/src/kmc-package/node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@sentry/node/node_modules/@sentry/core": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.6.tgz", - "integrity": "sha512-biEotGRr44/vBCOegkTfC9rwqaqRKIpFljKGyYU6/NtzMRooktqOhjmjmItNCMRknArdeaQwA8lk2jcZDXX3Og==", + "developer/src/kmc-package/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "@sentry/hub": "6.19.6", - "@sentry/minimal": "6.19.6", - "@sentry/types": "6.19.6", - "@sentry/utils": "6.19.6", - "tslib": "^1.9.3" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@sentry/node/node_modules/@sentry/hub": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.19.6.tgz", - "integrity": "sha512-PuEOBZxvx3bjxcXmWWZfWXG+orojQiWzv9LQXjIgroVMKM/GG4QtZbnWl1hOckUj7WtKNl4hEGO2g/6PyCV/vA==", + "developer/src/kmc-package/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc-package/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", "dependencies": { - "@sentry/types": "6.19.6", - "@sentry/utils": "6.19.6", - "tslib": "^1.9.3" + "has-flag": "^3.0.0" }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/@sentry/node/node_modules/@sentry/minimal": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.19.6.tgz", - "integrity": "sha512-T1NKcv+HTlmd8EbzUgnGPl4ySQGHWMCyZ8a8kXVMZOPDzphN3fVIzkYzWmSftCWp0rpabXPt9aRF2mfBKU+mAQ==", + "developer/src/kmc-package/node_modules/supports-color/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "developer/src/kmc-package/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": { - "@sentry/hub": "6.19.6", - "@sentry/types": "6.19.6", - "tslib": "^1.9.3" + "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": ">=6" + "node": ">=10.0.0" + }, + "peerDependencies": { + "typescript": ">=2.7" } }, - "node_modules/@sentry/node/node_modules/@sentry/types": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.19.6.tgz", - "integrity": "sha512-QH34LMJidEUPZK78l+Frt3AaVFJhEmIi05Zf8WHd9/iTt+OqvCHBgq49DDr1FWFqyYWm/QgW/3bIoikFpfsXyQ==", + "developer/src/kmc-package/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": ">=6" + "node": ">=0.3.1" } }, - "node_modules/@sentry/node/node_modules/@sentry/utils": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.19.6.tgz", - "integrity": "sha512-fAMWcsguL0632eWrROp/vhPgI7sBj/JROWVPzpabwVkm9z3m1rQm6iLFn4qfkZL8Ozy6NVZPXOQ7EXmeU24byg==", + "developer/src/kmc/node_modules/@types/mocha": { + "version": "5.2.7", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc/node_modules/@types/node": { + "version": "10.17.60", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", "dependencies": { - "@sentry/types": "6.19.6", - "tslib": "^1.9.3" + "color-convert": "^1.9.0" }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==", + "developer/src/kmc/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, "engines": { - "node": ">=6" + "node": ">=4" } }, - "node_modules/@sentry/utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", - "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", + "developer/src/kmc/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", "dependencies": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - }, - "engines": { - "node": ">=6" + "color-name": "1.1.3" } }, - "node_modules/@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==", + "developer/src/kmc/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "developer/src/kmc/node_modules/commander": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.0.tgz", + "integrity": "sha512-zS5PnTI22FIRM6ylNW8G4Ap0IEOyk62fhLSD0+uHRT9McRCLGpkVNvao4bjimpK/GShynyQkFFxHhwMcETmduA==", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" + "node": ">=14" } }, - "node_modules/@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", + "developer/src/kmc/node_modules/js-yaml": { + "version": "4.0.0", "dev": true, + "license": "MIT", "dependencies": { - "type-detect": "4.0.8" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "developer/src/kmc/node_modules/minimatch": { + "version": "3.0.4", "dev": true, + "license": "ISC", "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "developer/src/kmc/node_modules/mocha": { + "version": "8.4.0", "dev": true, + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "@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" } }, - "node_modules/@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "node_modules/@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", - "dev": true + "developer/src/kmc/node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", + "developer/src/kmc/node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "defer-to-connect": "^2.0.0" + "has-flag": "^4.0.0" }, "engines": { "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "node_modules/@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "node_modules/@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "node_modules/@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true + "developer/src/kmc/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" }, - "node_modules/@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", + "developer/src/kmc/node_modules/supports-color": { + "version": "5.5.0", "dev": true, + "license": "MIT", "dependencies": { - "@types/connect": "*", - "@types/node": "*" + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "node_modules/@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", - "dependencies": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "node_modules/@types/chai": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", - "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", - "dev": true - }, - "node_modules/@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", + "developer/src/kmc/node_modules/supports-color/node_modules/has-flag": { + "version": "3.0.0", "dev": true, - "dependencies": { - "@types/node": "*" + "license": "MIT", + "engines": { + "node": ">=4" } }, - "node_modules/@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "node_modules/@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", + "developer/src/kmc/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": { - "@types/node": "*" + "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" } }, - "node_modules/@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", + "developer/src/kmc/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, - "dependencies": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" + "engines": { + "node": ">=0.3.1" } }, - "node_modules/@types/express-serve-static-core": { - "version": "4.17.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "dev": true, + "developer/src/server": { + "name": "@keymanapp/developer-server", + "license": "MIT", "dependencies": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" + "@sentry/node": "^6.16.1", + "chalk": "^4.1.2", + "express": "^4.17.2", + "multer": "^1.4.5-lts.1", + "ngrok": "^4.2.2", + "open": "^8.4.0", + "ws": "^8.3.0" + }, + "devDependencies": { + "@keymanapp/keyman-version": "*", + "@keymanapp/resources-gosh": "*", + "@types/chai": "^4.3.0", + "@types/express": "^4.17.13", + "@types/mocha": "^9.1.0", + "@types/multer": "^1.4.7", + "@types/node": "^17.0.0", + "@types/ws": "^8.2.2", + "chai": "^4.3.4", + "copyfiles": "^2.4.1", + "mocha": "^10.0.0", + "ts-node": "^10.9.1", + "tsc-watch": "^4.5.0", + "typescript": "^4.9.5" + }, + "optionalDependencies": { + "hetrodo-node-hide-console-window-napi": "keymanapp/hetrodo-node-hide-console-window-napi#keyman-15.0", + "node-windows-trayicon": "keymanapp/node-windows-trayicon#keyman-16.0" } }, - "node_modules/@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" + "developer/src/server/node_modules/@types/mocha": { + "version": "9.1.1", + "dev": true, + "license": "MIT" }, - "node_modules/@types/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==" + "developer/src/server/node_modules/@types/node": { + "version": "17.0.45", + "dev": true, + "license": "MIT" }, - "node_modules/@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", + "developer/src/server/node_modules/busboy": { + "version": "1.6.0", "dependencies": { - "@types/node": "*" + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" } }, - "node_modules/@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "node_modules/@types/mocha": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", - "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", - "dev": true - }, - "node_modules/@types/multer": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", - "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", - "dev": true, + "developer/src/server/node_modules/multer": { + "version": "1.4.5-lts.1", + "license": "MIT", "dependencies": { - "@types/express": "*" + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" } }, - "node_modules/@types/node": { - "version": "11.15.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.54.tgz", - "integrity": "sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g==" - }, - "node_modules/@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "node_modules/@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "node_modules/@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "dependencies": { - "@types/node": "*" + "developer/src/server/node_modules/streamsearch": { + "version": "1.1.0", + "engines": { + "node": ">=10.0.0" } }, - "node_modules/@types/semver": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", - "dev": true - }, - "node_modules/@types/serve-static": { - "version": "1.13.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", - "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, + "node_modules/@actions/core": { + "version": "1.9.1", + "license": "MIT", "dependencies": { - "@types/mime": "^1", - "@types/node": "*" + "@actions/http-client": "^2.0.1", + "uuid": "^8.3.2" } }, - "node_modules/@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", - "dev": true, + "node_modules/@actions/github": { + "version": "2.2.0", + "license": "MIT", "dependencies": { - "@types/node": "*" + "@actions/http-client": "^1.0.3", + "@octokit/graphql": "^4.3.1", + "@octokit/rest": "^16.43.1" } }, - "node_modules/@types/xml2js": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz", - "integrity": "sha512-CHiCKIihl1pychwR2RNX5mAYmJDACgFVCMT5OArMaO3erzwXVcBqPcusr+Vl8yeeXukxZqtF8mZioqX+mpjjdw==", - "dev": true, + "node_modules/@actions/github/node_modules/@actions/http-client": { + "version": "1.0.11", + "license": "MIT", "dependencies": { - "@types/node": "*" + "tunnel": "0.0.6" } }, - "node_modules/@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", - "optional": true, + "node_modules/@actions/http-client": { + "version": "2.0.1", + "license": "MIT", "dependencies": { - "@types/node": "*" + "tunnel": "^0.0.6" } }, - "node_modules/abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "optional": true + "node_modules/@bcoe/v8-coverage": { + "version": "0.2.3", + "dev": true, + "license": "MIT" }, - "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - }, + "node_modules/@colors/colors": { + "version": "1.5.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">=0.1.90" } }, - "node_modules/acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", + "node_modules/@cspotcode/source-map-support": { + "version": "0.8.1", "dev": true, - "bin": { - "acorn": "bin/acorn" + "license": "MIT", + "dependencies": { + "@jridgewell/trace-mapping": "0.3.9" }, "engines": { - "node": ">=0.4.0" + "node": ">=12" } }, - "node_modules/acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "optional": true + }, + "node_modules/@istanbuljs/schema": { + "version": "0.1.3", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.4.0" + "node": ">=8" } }, - "node_modules/agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "dependencies": { - "debug": "4" - }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "optional": 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": ">=6.0.0" } }, - "node_modules/ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } + "license": "MIT" }, - "node_modules/anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.9", "dev": true, + "license": "MIT", "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" + "@jridgewell/resolve-uri": "^3.0.3", + "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" + "node_modules/@keymanapp/auto-history-action": { + "resolved": "resources/build/version", + "link": true }, - "node_modules/aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "devOptional": true + "node_modules/@keymanapp/common-types": { + "resolved": "common/web/types", + "link": true }, - "node_modules/are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "optional": true, - "dependencies": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } + "node_modules/@keymanapp/developer-server": { + "resolved": "developer/src/server", + "link": true }, - "node_modules/arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true + "node_modules/@keymanapp/hextobin": { + "resolved": "common/tools/hextobin", + "link": true }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true + "node_modules/@keymanapp/input-processor": { + "resolved": "common/web/input-processor", + "link": true }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + "node_modules/@keymanapp/keyboard-processor": { + "resolved": "common/web/keyboard-processor", + "link": true }, - "node_modules/array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true + "node_modules/@keymanapp/keyman-version": { + "resolved": "common/web/keyman-version", + "link": true }, - "node_modules/asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "optional": true, - "dependencies": { - "safer-buffer": "~2.1.0" - } + "node_modules/@keymanapp/kmc": { + "resolved": "developer/src/kmc", + "link": true }, - "node_modules/assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true, - "engines": { - "node": ">=0.8" - } + "node_modules/@keymanapp/kmc-keyboard": { + "resolved": "developer/src/kmc-keyboard", + "link": true }, - "node_modules/assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true, - "engines": { - "node": "*" - } + "node_modules/@keymanapp/kmc-model": { + "resolved": "developer/src/kmc-model", + "link": true }, - "node_modules/asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "optional": true + "node_modules/@keymanapp/kmc-model-info": { + "resolved": "developer/src/kmc-model-info", + "link": true }, - "node_modules/atob-lite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", - "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==" + "node_modules/@keymanapp/kmc-package": { + "resolved": "developer/src/kmc-package", + "link": true }, - "node_modules/aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "optional": true, - "engines": { - "node": "*" - } + "node_modules/@keymanapp/ldml-keyboard-constants": { + "resolved": "core/include/ldml", + "link": true }, - "node_modules/aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "optional": true + "node_modules/@keymanapp/lexical-model-layer": { + "resolved": "common/predictive-text", + "link": true }, - "node_modules/balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "devOptional": true + "node_modules/@keymanapp/lm-message-types": { + "resolved": "common/web/lm-message-types", + "link": true }, - "node_modules/base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true, - "engines": { - "node": "^4.5.0 || >= 5.9" - } + "node_modules/@keymanapp/lm-worker": { + "resolved": "common/web/lm-worker", + "link": true }, - "node_modules/bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, - "dependencies": { - "tweetnacl": "^0.14.3" - } + "node_modules/@keymanapp/models-templates": { + "resolved": "common/models/templates", + "link": true }, - "node_modules/before-after-hook": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" + "node_modules/@keymanapp/models-types": { + "resolved": "common/models/types", + "link": true }, - "node_modules/binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true, + "node_modules/@keymanapp/models-wordbreakers": { + "resolved": "common/models/wordbreakers", + "link": true + }, + "node_modules/@keymanapp/recorder-core": { + "resolved": "common/web/recorder", + "link": true + }, + "node_modules/@keymanapp/resources-gosh": { + "resolved": "resources/gosh", + "link": true + }, + "node_modules/@keymanapp/sourcemap-path-remapper": { + "resolved": "common/tools/sourcemap-path-remapper", + "link": true + }, + "node_modules/@keymanapp/web-sentry-manager": { + "resolved": "common/web/sentry-manager", + "link": true + }, + "node_modules/@keymanapp/web-utils": { + "resolved": "common/web/utils", + "link": true + }, + "node_modules/@npmcli/fs": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz", + "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.1.3", + "semver": "^7.3.5" + }, "engines": { - "node": ">=8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "node_modules/@npmcli/move-file": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz", + "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==", + "deprecated": "This functionality has been moved to @npmcli/fs", "optional": true, "dependencies": { - "file-uri-to-path": "1.0.0" + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", - "dependencies": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">= 0.8" + "node": ">=10" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@octokit/auth-token": { + "version": "2.5.0", + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "@octokit/types": "^6.0.3" } }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "node_modules/@octokit/core": { + "version": "3.6.0", + "license": "MIT", + "peer": true, + "dependencies": { + "@octokit/auth-token": "^2.4.4", + "@octokit/graphql": "^4.5.8", + "@octokit/request": "^5.6.3", + "@octokit/request-error": "^2.0.5", + "@octokit/types": "^6.0.3", + "before-after-hook": "^2.2.0", + "universal-user-agent": "^6.0.0" + } }, - "node_modules/brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, + "node_modules/@octokit/endpoint": { + "version": "6.0.12", + "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "@octokit/types": "^6.0.3", + "is-plain-object": "^5.0.0", + "universal-user-agent": "^6.0.0" } }, - "node_modules/braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, + "node_modules/@octokit/graphql": { + "version": "4.8.0", + "license": "MIT", "dependencies": { - "fill-range": "^7.0.1" - }, - "engines": { - "node": ">=8" + "@octokit/request": "^5.6.0", + "@octokit/types": "^6.0.3", + "universal-user-agent": "^6.0.0" } }, - "node_modules/browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true + "node_modules/@octokit/openapi-types": { + "version": "11.2.0", + "license": "MIT" }, - "node_modules/browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, + "node_modules/@octokit/plugin-paginate-rest": { + "version": "1.1.2", + "license": "MIT", "dependencies": { - "https-proxy-agent": "^2.2.1" + "@octokit/types": "^2.0.1" } }, - "node_modules/browserstack-local": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.9.tgz", - "integrity": "sha512-V+q8HQwRQFr9nd32xR66ZZ3VDWa3Kct4IMMudhKgcuD7cWrvvFARZOibx71II+Rf7P5nMQpWWxl9z/3p927nbg==", - "dev": true, + "node_modules/@octokit/plugin-paginate-rest/node_modules/@octokit/types": { + "version": "2.16.2", + "license": "MIT", "dependencies": { - "https-proxy-agent": "^4.0.0", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" + "@types/node": ">= 8" } }, - "node_modules/browserstack-local/node_modules/agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true, - "engines": { - "node": ">= 6.0.0" + "node_modules/@octokit/plugin-request-log": { + "version": "1.0.4", + "license": "MIT", + "peerDependencies": { + "@octokit/core": ">=3" } }, - "node_modules/browserstack-local/node_modules/https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, + "node_modules/@octokit/plugin-rest-endpoint-methods": { + "version": "2.4.0", + "license": "MIT", "dependencies": { - "agent-base": "5", - "debug": "4" - }, - "engines": { - "node": ">= 6.0.0" + "@octokit/types": "^2.0.1", + "deprecation": "^2.3.1" } }, - "node_modules/browserstack/node_modules/agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, + "node_modules/@octokit/plugin-rest-endpoint-methods/node_modules/@octokit/types": { + "version": "2.16.2", + "license": "MIT", "dependencies": { - "es6-promisify": "^5.0.0" - }, - "engines": { - "node": ">= 4.0.0" + "@types/node": ">= 8" } }, - "node_modules/browserstack/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, + "node_modules/@octokit/request": { + "version": "5.6.3", + "license": "MIT", "dependencies": { - "ms": "^2.1.1" + "@octokit/endpoint": "^6.0.1", + "@octokit/request-error": "^2.1.0", + "@octokit/types": "^6.16.1", + "is-plain-object": "^5.0.0", + "node-fetch": "^2.6.7", + "universal-user-agent": "^6.0.0" } }, - "node_modules/browserstack/node_modules/https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, + "node_modules/@octokit/request-error": { + "version": "2.1.0", + "license": "MIT", "dependencies": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - }, - "engines": { - "node": ">= 4.5.0" + "@octokit/types": "^6.0.3", + "deprecation": "^2.0.0", + "once": "^1.4.0" } }, - "node_modules/btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==" - }, - "node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "engines": { - "node": "*" + "node_modules/@octokit/rest": { + "version": "16.43.2", + "license": "MIT", + "dependencies": { + "@octokit/auth-token": "^2.4.0", + "@octokit/plugin-paginate-rest": "^1.1.1", + "@octokit/plugin-request-log": "^1.0.0", + "@octokit/plugin-rest-endpoint-methods": "2.4.0", + "@octokit/request": "^5.2.0", + "@octokit/request-error": "^1.0.2", + "atob-lite": "^2.0.0", + "before-after-hook": "^2.0.0", + "btoa-lite": "^1.0.0", + "deprecation": "^2.0.0", + "lodash.get": "^4.4.2", + "lodash.set": "^4.3.2", + "lodash.uniq": "^4.5.0", + "octokit-pagination-methods": "^1.1.0", + "once": "^1.4.0", + "universal-user-agent": "^4.0.0" } }, - "node_modules/buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "node_modules/@octokit/rest/node_modules/@octokit/request-error": { + "version": "1.2.1", + "license": "MIT", + "dependencies": { + "@octokit/types": "^2.0.0", + "deprecation": "^2.0.0", + "once": "^1.4.0" + } }, - "node_modules/bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", - "engines": { - "node": ">= 0.8" + "node_modules/@octokit/rest/node_modules/@octokit/types": { + "version": "2.16.2", + "license": "MIT", + "dependencies": { + "@types/node": ">= 8" } }, - "node_modules/cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", - "engines": { - "node": ">=10.6.0" + "node_modules/@octokit/rest/node_modules/universal-user-agent": { + "version": "4.0.1", + "license": "ISC", + "dependencies": { + "os-name": "^3.1.0" } }, - "node_modules/cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", + "node_modules/@octokit/types": { + "version": "6.34.0", + "license": "MIT", "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" + "@octokit/openapi-types": "^11.2.0" } }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "node_modules/@sentry/browser": { + "version": "5.30.0", + "license": "BSD-3-Clause", + "dependencies": { + "@sentry/core": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, "engines": { "node": ">=6" } }, - "node_modules/caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "optional": true - }, - "node_modules/chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", + "node_modules/@sentry/cli": { + "version": "2.2.0", "dev": true, + "hasInstallScript": true, + "license": "BSD-3-Clause", "dependencies": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" + "https-proxy-agent": "^5.0.0", + "node-fetch": "^2.6.7", + "npmlog": "^6.0.1", + "progress": "^2.0.3", + "proxy-from-env": "^1.1.0", + "which": "^2.0.2" + }, + "bin": { + "sentry-cli": "bin/sentry-cli" }, "engines": { - "node": ">=4" + "node": ">= 12" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "node_modules/@sentry/core": { + "version": "5.30.0", + "license": "BSD-3-Clause", "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" + "@sentry/hub": "5.30.0", + "@sentry/minimal": "5.30.0", + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" + "node": ">=6" } }, - "node_modules/check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true, + "node_modules/@sentry/hub": { + "version": "5.30.0", + "license": "BSD-3-Clause", + "dependencies": { + "@sentry/types": "5.30.0", + "@sentry/utils": "5.30.0", + "tslib": "^1.9.3" + }, "engines": { - "node": "*" + "node": ">=6" } }, - "node_modules/chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, + "node_modules/@sentry/minimal": { + "version": "5.30.0", + "license": "BSD-3-Clause", "dependencies": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" + "@sentry/hub": "5.30.0", + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" }, "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.1" + "node": ">=6" } }, - "node_modules/chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "optional": true, + "node_modules/@sentry/node": { + "version": "6.19.6", + "license": "BSD-3-Clause", + "dependencies": { + "@sentry/core": "6.19.6", + "@sentry/hub": "6.19.6", + "@sentry/types": "6.19.6", + "@sentry/utils": "6.19.6", + "cookie": "^0.4.1", + "https-proxy-agent": "^5.0.0", + "lru_map": "^0.3.3", + "tslib": "^1.9.3" + }, "engines": { - "node": ">=10" + "node": ">=6" } }, - "node_modules/cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, + "node_modules/@sentry/node/node_modules/@sentry/core": { + "version": "6.19.6", + "license": "BSD-3-Clause", "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" + "@sentry/hub": "6.19.6", + "@sentry/minimal": "6.19.6", + "@sentry/types": "6.19.6", + "@sentry/utils": "6.19.6", + "tslib": "^1.9.3" + }, + "engines": { + "node": ">=6" } }, - "node_modules/cliui/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cliui/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, + "node_modules/@sentry/node/node_modules/@sentry/hub": { + "version": "6.19.6", + "license": "BSD-3-Clause", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" + "@sentry/types": "6.19.6", + "@sentry/utils": "6.19.6", + "tslib": "^1.9.3" }, "engines": { - "node": ">=8" + "node": ">=6" } }, - "node_modules/cliui/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, + "node_modules/@sentry/node/node_modules/@sentry/minimal": { + "version": "6.19.6", + "license": "BSD-3-Clause", "dependencies": { - "ansi-regex": "^5.0.0" + "@sentry/hub": "6.19.6", + "@sentry/types": "6.19.6", + "tslib": "^1.9.3" }, "engines": { - "node": ">=8" - } - }, - "node_modules/clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "dependencies": { - "mimic-response": "^1.0.0" + "node": ">=6" } }, - "node_modules/code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true, + "node_modules/@sentry/node/node_modules/@sentry/types": { + "version": "6.19.6", + "license": "BSD-3-Clause", "engines": { - "node": ">=0.10.0" + "node": ">=6" } }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "node_modules/@sentry/node/node_modules/@sentry/utils": { + "version": "6.19.6", + "license": "BSD-3-Clause", "dependencies": { - "color-name": "~1.1.4" + "@sentry/types": "6.19.6", + "tslib": "^1.9.3" }, "engines": { - "node": ">=7.0.0" + "node": ">=6" } }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "node_modules/color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true, - "bin": { - "color-support": "bin.js" + "node_modules/@sentry/types": { + "version": "5.30.0", + "license": "BSD-3-Clause", + "engines": { + "node": ">=6" } }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "optional": true, + "node_modules/@sentry/utils": { + "version": "5.30.0", + "license": "BSD-3-Clause", "dependencies": { - "delayed-stream": "~1.0.0" + "@sentry/types": "5.30.0", + "tslib": "^1.9.3" }, "engines": { - "node": ">= 0.8" + "node": ">=6" } }, - "node_modules/commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" - }, - "node_modules/compress-brotli": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.6.tgz", - "integrity": "sha512-au99/GqZtUtiCBliqLFbWlhnCxn+XSYjwZ77q6mKN4La4qOXDoLVPZ50iXr0WmAyMxl8yqoq3Yq4OeQNPPkyeQ==", - "dependencies": { - "@types/json-buffer": "~3.0.0", - "json-buffer": "~3.0.1" - }, + "node_modules/@sindresorhus/is": { + "version": "4.6.0", + "license": "MIT", "engines": { - "node": ">= 12" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" } }, - "node_modules/concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "devOptional": true - }, - "node_modules/concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "engines": [ - "node >= 0.8" - ], + "node_modules/@sinonjs/commons": { + "version": "1.8.3", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" + "type-detect": "4.0.8" } }, - "node_modules/connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", + "node_modules/@sinonjs/formatio": { + "version": "3.2.2", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "engines": { - "node": ">= 0.10.0" + "@sinonjs/commons": "^1", + "@sinonjs/samsam": "^3.1.0" } }, - "node_modules/connect/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/@sinonjs/samsam": { + "version": "3.3.3", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "ms": "2.0.0" + "@sinonjs/commons": "^1.3.0", + "array-from": "^2.1.1", + "lodash": "^4.17.15" } }, - "node_modules/connect/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "node_modules/console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "devOptional": true + "node_modules/@sinonjs/text-encoding": { + "version": "0.7.1", + "dev": true, + "license": "(Unlicense OR Apache-2.0)" }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "dependencies": { - "safe-buffer": "5.2.1" - }, + "node_modules/@socket.io/base64-arraybuffer": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.6.0" } }, - "node_modules/content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==", + "node_modules/@szmarczak/http-timer": { + "version": "4.0.6", + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=10" } }, - "node_modules/convert-source-map": { + "node_modules/@tootallnate/once": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" - }, - "node_modules/cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz", + "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==", + "optional": true, "engines": { - "node": ">= 0.6" + "node": ">= 10" } }, - "node_modules/cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + "node_modules/@tsconfig/node10": { + "version": "1.0.8", + "dev": true, + "license": "MIT" }, - "node_modules/copyfiles": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", - "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", + "node_modules/@tsconfig/node12": { + "version": "1.0.9", "dev": true, - "dependencies": { - "glob": "^7.0.5", - "minimatch": "^3.0.3", - "mkdirp": "^1.0.4", - "noms": "0.0.0", - "through2": "^2.0.1", - "untildify": "^4.0.0", - "yargs": "^16.1.0" - }, - "bin": { - "copyfiles": "copyfiles", - "copyup": "copyfiles" - } + "license": "MIT" }, - "node_modules/copyfiles/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "node_modules/@tsconfig/node14": { + "version": "1.0.1", "dev": true, - "bin": { - "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" - } + "license": "MIT" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" + "node_modules/@tsconfig/node16": { + "version": "1.0.2", + "dev": true, + "license": "MIT" }, - "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "node_modules/@types/body-parser": { + "version": "1.19.2", "dev": true, + "license": "MIT", "dependencies": { - "object-assign": "^4", - "vary": "^1" - }, - "engines": { - "node": ">= 0.10" + "@types/connect": "*", + "@types/node": "*" } }, - "node_modules/create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "node_modules/cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, + "node_modules/@types/cacheable-request": { + "version": "6.0.2", + "license": "MIT", "dependencies": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - }, - "engines": { - "node": ">= 8" + "@types/http-cache-semantics": "*", + "@types/keyv": "*", + "@types/node": "*", + "@types/responselike": "*" } }, - "node_modules/custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true + "node_modules/@types/chai": { + "version": "4.3.0", + "dev": true, + "license": "MIT" }, - "node_modules/dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "optional": true, + "node_modules/@types/component-emitter": { + "version": "1.2.11", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/connect": { + "version": "3.4.35", + "dev": true, + "license": "MIT", "dependencies": { - "assert-plus": "^1.0.0" - }, - "engines": { - "node": ">=0.10" + "@types/node": "*" } }, - "node_modules/date-format": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.6.tgz", - "integrity": "sha512-B9vvg5rHuQ8cbUXE/RMWMyX2YA5TecT3jKF5fLtGNlzPlU7zblSPmAm2OImDbWL+LDOQ6pUm+4LOFz+ywS41Zw==", + "node_modules/@types/cookie": { + "version": "0.4.1", "dev": true, - "engines": { - "node": ">=4.0" - } + "license": "MIT" }, - "node_modules/debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } + "node_modules/@types/cors": { + "version": "2.8.12", + "dev": true, + "license": "MIT" }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "engines": { - "node": ">=0.10.0" + "node_modules/@types/express": { + "version": "4.17.13", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.18", + "@types/qs": "*", + "@types/serve-static": "*" } }, - "node_modules/decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "node_modules/@types/express-serve-static-core": { + "version": "4.17.28", + "dev": true, + "license": "MIT", "dependencies": { - "mimic-response": "^3.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*" } }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/@types/git-diff": { + "version": "2.0.3", + "dev": true, + "license": "MIT" }, - "node_modules/deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", + "node_modules/@types/http-cache-semantics": { + "version": "4.0.1", + "license": "MIT" + }, + "node_modules/@types/istanbul-lib-coverage": { + "version": "2.0.4", "dev": true, + "license": "MIT" + }, + "node_modules/@types/json-buffer": { + "version": "3.0.0", + "license": "MIT" + }, + "node_modules/@types/keyv": { + "version": "3.1.4", + "license": "MIT", "dependencies": { - "type-detect": "^4.0.0" - }, - "engines": { - "node": ">=0.12" + "@types/node": "*" } }, - "node_modules/defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", - "engines": { - "node": ">=10" - } + "node_modules/@types/mime": { + "version": "1.3.2", + "dev": true, + "license": "MIT" }, - "node_modules/define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==", - "engines": { - "node": ">=8" - } + "node_modules/@types/mocha": { + "version": "7.0.2", + "dev": true, + "license": "MIT" }, - "node_modules/delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "optional": true, - "engines": { - "node": ">=0.4.0" + "node_modules/@types/multer": { + "version": "1.4.7", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/express": "*" } }, - "node_modules/delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "devOptional": true + "node_modules/@types/node": { + "version": "11.15.54", + "license": "MIT" }, - "node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=", - "engines": { - "node": ">= 0.6" - } + "node_modules/@types/qs": { + "version": "6.9.7", + "dev": true, + "license": "MIT" }, - "node_modules/deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" + "node_modules/@types/range-parser": { + "version": "1.2.4", + "dev": true, + "license": "MIT" }, - "node_modules/destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + "node_modules/@types/responselike": { + "version": "1.0.0", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, - "node_modules/di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true + "node_modules/@types/semver": { + "version": "7.3.13", + "dev": true, + "license": "MIT" }, - "node_modules/diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", + "node_modules/@types/serve-static": { + "version": "1.13.10", "dev": true, - "engines": { - "node": ">=0.3.1" + "license": "MIT", + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" } }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "node_modules/@types/ws": { + "version": "8.5.3", "dev": true, + "license": "MIT", "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" + "@types/node": "*" } }, - "node_modules/dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", + "node_modules/@types/xml2js": { + "version": "0.4.9", "dev": true, + "license": "MIT", "dependencies": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" + "@types/node": "*" } }, - "node_modules/duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "node_modules/ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", + "node_modules/@types/yauzl": { + "version": "2.9.2", + "license": "MIT", "optional": true, "dependencies": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" + "@types/node": "*" } }, - "node_modules/edge-launcher": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/edge-launcher/-/edge-launcher-1.2.2.tgz", - "integrity": "sha1-60Cq+9Bnpup27/+rBke81VCbN7I=", - "dev": true + "node_modules/@ungap/promise-all-settled": { + "version": "1.1.2", + "dev": true, + "license": "ISC" }, - "node_modules/ee-first": { + "node_modules/abbrev": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "node_modules/encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=", - "engines": { - "node": ">= 0.8" - } + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "optional": true }, - "node_modules/end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "node_modules/accepts": { + "version": "1.3.8", + "license": "MIT", "dependencies": { - "once": "^1.4.0" + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/engine.io": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.0.tgz", - "integrity": "sha512-OgxY1c/RuCSeO/rTr8DIFXx76IzUUft86R7/P7MMbbkuzeqJoTNw2lmeD91IyGz41QYleIIjWeMJGgug043sfQ==", + "node_modules/acorn": { + "version": "8.7.0", "dev": true, - "dependencies": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.11.0" + "license": "MIT", + "bin": { + "acorn": "bin/acorn" }, "engines": { - "node": ">=10.0.0" + "node": ">=0.4.0" } }, - "node_modules/engine.io-parser": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", - "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", + "node_modules/acorn-walk": { + "version": "8.2.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=10.0.0" + "node": ">=0.4.0" } }, - "node_modules/ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "node_modules/entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node_modules/agent-base": { + "version": "6.0.2", + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/agentkeepalive": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.2.1.tgz", + "integrity": "sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==", "optional": true, + "dependencies": { + "debug": "^4.1.0", + "depd": "^1.1.2", + "humanize-ms": "^1.2.1" + }, "engines": { - "node": ">=6" + "node": ">= 8.0.0" } }, - "node_modules/es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "node_modules/es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "optional": true, "dependencies": { - "es6-promise": "^4.0.3" - } - }, - "node_modules/es6-shim": { - "version": "0.35.6", - "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.6.tgz", - "integrity": "sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA==" - }, - "node_modules/escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true, + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": ">=8" } }, - "node_modules/escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "node_modules/ansi-colors": { + "version": "4.1.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.8.0" + "node": ">=6" } }, - "node_modules/esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "node_modules/ansi-regex": { + "version": "2.1.1", "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=", + "node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, "engines": { - "node": ">= 0.6" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", + "node_modules/anymatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", "dependencies": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "node_modules/eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "node_modules/execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "dependencies": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" }, "engines": { - "node": ">=6" + "node": ">= 8" } }, - "node_modules/execa/node_modules/cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "node_modules/append-field": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/aproba": { + "version": "2.0.0", + "devOptional": true, + "license": "ISC" + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "devOptional": true, + "license": "ISC", "dependencies": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" }, "engines": { - "node": ">=4.8" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/execa/node_modules/get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", + "node_modules/are-we-there-yet/node_modules/readable-stream": { + "version": "3.6.0", + "devOptional": true, + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" }, "engines": { - "node": ">=6" + "node": ">= 6" } }, - "node_modules/execa/node_modules/path-key": { + "node_modules/arg": { + "version": "4.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/argparse": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/array-from": { + "version": "2.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": "*" } }, - "node_modules/execa/node_modules/semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", - "bin": { - "semver": "bin/semver" - } + "node_modules/atob-lite": { + "version": "2.0.0", + "license": "MIT" }, - "node_modules/execa/node_modules/shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "dependencies": { - "shebang-regex": "^1.0.0" - }, + "node_modules/balanced-match": { + "version": "1.0.0", + "devOptional": true, + "license": "MIT" + }, + "node_modules/base64id": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": "^4.5.0 || >= 5.9" } }, - "node_modules/execa/node_modules/shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==", + "node_modules/before-after-hook": { + "version": "2.2.2", + "license": "Apache-2.0" + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/execa/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "optional": true, "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "which": "bin/which" + "file-uri-to-path": "1.0.0" } }, - "node_modules/express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", + "node_modules/body-parser": { + "version": "1.19.2", + "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.19.2", - "content-disposition": "0.5.4", + "bytes": "3.1.2", "content-type": "~1.0.4", - "cookie": "0.4.2", - "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", "qs": "6.9.7", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "raw-body": "2.4.3", + "type-is": "~1.6.18" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 0.8" } }, - "node_modules/express/node_modules/debug": { + "node_modules/body-parser/node_modules/debug": { "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", "dependencies": { "ms": "2.0.0" } }, - "node_modules/express/node_modules/ms": { + "node_modules/body-parser/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "license": "MIT" }, - "node_modules/extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "devOptional": true + "node_modules/brace-expansion": { + "version": "1.1.11", + "devOptional": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", + "node_modules/braces": { + "version": "3.0.2", + "dev": true, + "license": "MIT", "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" + "fill-range": "^7.0.1" }, "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" + "node": ">=8" } }, - "node_modules/extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "engines": [ - "node >=0.6.0" - ], - "optional": true - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "optional": true + "node_modules/browser-stdout": { + "version": "1.3.1", + "dev": true, + "license": "ISC" }, - "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", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "optional": true - }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", + "node_modules/browserstack": { + "version": "1.5.3", + "dev": true, + "license": "MIT", "dependencies": { - "pend": "~1.2.0" + "https-proxy-agent": "^2.2.1" } }, - "node_modules/file": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/file/-/file-0.2.2.tgz", - "integrity": "sha1-w9/Y+M81Na5FXCtCPC5SY112tNM=", - "dev": true + "node_modules/browserstack-local": { + "version": "1.4.9", + "dev": true, + "license": "MIT", + "dependencies": { + "https-proxy-agent": "^4.0.0", + "is-running": "^2.1.0", + "ps-tree": "=1.2.0", + "temp-fs": "^0.9.9" + } }, - "node_modules/file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true + "node_modules/browserstack-local/node_modules/agent-base": { + "version": "5.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6.0.0" + } }, - "node_modules/fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "node_modules/browserstack-local/node_modules/https-proxy-agent": { + "version": "4.0.0", "dev": true, + "license": "MIT", "dependencies": { - "to-regex-range": "^5.0.1" + "agent-base": "5", + "debug": "4" }, "engines": { - "node": ">=8" + "node": ">= 6.0.0" } }, - "node_modules/finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", + "node_modules/browserstack/node_modules/agent-base": { + "version": "4.3.0", + "dev": true, + "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" + "es6-promisify": "^5.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 4.0.0" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/browserstack/node_modules/debug": { + "version": "3.2.7", + "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.0.0" + "ms": "^2.1.1" } }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "node_modules/browserstack/node_modules/https-proxy-agent": { + "version": "2.2.4", "dev": true, + "license": "MIT", "dependencies": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "agent-base": "^4.3.0", + "debug": "^3.1.0" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true, - "bin": { - "flat": "cli.js" + "node": ">= 4.5.0" } }, - "node_modules/flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true + "node_modules/btoa-lite": { + "version": "1.0.0", + "license": "MIT" }, - "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", - "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], + "node_modules/buffer-crc32": { + "version": "0.2.13", + "license": "MIT", "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "node": "*" } }, - "node_modules/forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "optional": true, + "node_modules/buffer-from": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/bytes": { + "version": "3.1.2", + "license": "MIT", "engines": { - "node": "*" + "node": ">= 0.8" } }, - "node_modules/form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "optional": true, + "node_modules/c8": { + "version": "7.12.0", + "dev": true, + "license": "ISC", "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "@bcoe/v8-coverage": "^0.2.3", + "@istanbuljs/schema": "^0.1.3", + "find-up": "^5.0.0", + "foreground-child": "^2.0.0", + "istanbul-lib-coverage": "^3.2.0", + "istanbul-lib-report": "^3.0.0", + "istanbul-reports": "^3.1.4", + "rimraf": "^3.0.2", + "test-exclude": "^6.0.0", + "v8-to-istanbul": "^9.0.0", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9" + }, + "bin": { + "c8": "bin/c8.js" }, "engines": { - "node": ">= 0.12" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "engines": { - "node": ">= 0.6" + "node": ">=10.12.0" } }, - "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", + "node_modules/c8/node_modules/yargs-parser": { + "version": "20.2.9", + "dev": true, + "license": "ISC", "engines": { - "node": ">= 0.6" + "node": ">=10" } }, - "node_modules/from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "node_modules/fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", - "dev": true, + "node_modules/cacache": { + "version": "16.1.3", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz", + "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==", + "optional": true, "dependencies": { - "null-check": "^1.0.0" + "@npmcli/fs": "^2.1.0", + "@npmcli/move-file": "^2.0.0", + "chownr": "^2.0.0", + "fs-minipass": "^2.1.0", + "glob": "^8.0.1", + "infer-owner": "^1.0.4", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "mkdirp": "^1.0.4", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^9.0.0", + "tar": "^6.1.11", + "unique-filename": "^2.0.0" }, "engines": { - "node": ">=0.10.0" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", - "dev": true, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "optional": true, "dependencies": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=12" + "balanced-match": "^1.0.0" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "node_modules/cacache/node_modules/glob": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz", + "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==", "optional": true, "dependencies": { - "minipass": "^3.0.0" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^5.0.1", + "once": "^1.3.0" }, "engines": { - "node": ">= 8" + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "devOptional": true - }, - "node_modules/fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", - "dev": true, - "hasInstallScript": true, + "node_modules/cacache/node_modules/lru-cache": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.16.1.tgz", + "integrity": "sha512-9kkuMZHnLH/8qXARvYSjNvq8S1GYFFzynQTAfKeaJ0sIrR3PUPuu37Z+EiIANiZBvpfTf2B5y8ecDLSMWlLv+w==", "optional": true, - "os": [ - "darwin" - ], "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + "node": ">=12" } }, - "node_modules/gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "node_modules/cacache/node_modules/minimatch": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "optional": true, "dependencies": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=10" } }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, "engines": { - "node": "6.* || 8.* || >= 10.*" + "node": ">=10" } }, - "node_modules/get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true, + "node_modules/cacheable-lookup": { + "version": "5.0.4", + "license": "MIT", "engines": { - "node": "*" + "node": ">=10.6.0" } }, - "node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "node_modules/cacheable-request": { + "version": "7.0.2", + "license": "MIT", "dependencies": { - "pump": "^3.0.0" + "clone-response": "^1.0.2", + "get-stream": "^5.1.0", + "http-cache-semantics": "^4.0.0", + "keyv": "^4.0.0", + "lowercase-keys": "^2.0.0", + "normalize-url": "^6.0.1", + "responselike": "^2.0.0" }, "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "optional": true, - "dependencies": { - "assert-plus": "^1.0.0" + "node_modules/camelcase": { + "version": "5.3.1", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "devOptional": true, + "node_modules/chai": { + "version": "4.3.4", + "dev": true, + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^3.0.1", + "get-func-name": "^2.0.0", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": ">=4" } }, - "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "node_modules/chalk": { + "version": "4.1.2", + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">= 6" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/google-closure-compiler-java": { - "version": "20200224.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20200224.0.0.tgz", - "integrity": "sha512-palFcDoScauZjWIsGDzMK6h+IctcRb55I3wJX8Ko/DTSz72xwadRdKm0lGt8OoYL7SKEO+IjgD7s8XrAGpLnlQ==", - "dev": true + "node_modules/check-error": { + "version": "1.0.2", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } }, - "node_modules/got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", + "node_modules/chokidar": { + "version": "3.5.1", + "dev": true, + "license": "MIT", "dependencies": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "anymatch": "~3.1.1", + "braces": "~3.0.2", + "glob-parent": "~5.1.0", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.5.0" }, "engines": { - "node": ">=10.19.0" + "node": ">= 8.10.0" }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" + "optionalDependencies": { + "fsevents": "~2.3.1" } }, - "node_modules/graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "devOptional": true - }, - "node_modules/har-schema": { + "node_modules/chownr": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", "optional": true, "engines": { - "node": ">=4" + "node": ">=10" } }, - "node_modules/har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "deprecated": "this library is no longer supported", + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", "optional": true, - "dependencies": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - }, "engines": { "node": ">=6" } }, - "node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "node_modules/cliui": { + "version": "7.0.4", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "devOptional": true - }, - "node_modules/he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "node_modules/cliui/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", "dev": true, - "bin": { - "he": "bin/he" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/hetrodo-node-hide-console-window-napi": { - "name": "node-hide-console-window", - "version": "2.0.1", - "resolved": "git+ssh://git@github.com/keymanapp/hetrodo-node-hide-console-window-napi.git#858b23036a9963b40ad6ff3c5bacd421e5839b92", - "hasInstallScript": true, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.2", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/hpagent": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", - "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==", - "optional": true - }, - "node_modules/http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" - }, - "node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.0", "dev": true, + "license": "MIT", "dependencies": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "ansi-regex": "^5.0.0" }, "engines": { - "node": ">=8.0.0" + "node": ">=8" } }, - "node_modules/http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "optional": true, + "node_modules/clone-response": { + "version": "1.0.2", + "license": "MIT", "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - }, - "engines": { - "node": ">=0.8", - "npm": ">=1.3.7" + "mimic-response": "^1.0.0" } }, - "node_modules/http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "dependencies": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" - }, + "node_modules/code-point-at": { + "version": "1.1.0", + "dev": true, + "license": "MIT", "engines": { - "node": ">=10.19.0" + "node": ">=0.10.0" } }, - "node_modules/https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", + "node_modules/color-convert": { + "version": "2.0.1", + "license": "MIT", "dependencies": { - "agent-base": "6", - "debug": "4" + "color-name": "~1.1.4" }, "engines": { - "node": ">= 6" + "node": ">=7.0.0" } }, - "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "node_modules/color-name": { + "version": "1.1.4", + "license": "MIT" + }, + "node_modules/color-support": { + "version": "1.1.3", + "devOptional": true, + "license": "ISC", + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/commander": { + "version": "3.0.2", + "license": "MIT" + }, + "node_modules/component-emitter": { + "version": "1.3.0", + "dev": true, + "license": "MIT" + }, + "node_modules/compress-brotli": { + "version": "1.3.6", + "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "@types/json-buffer": "~3.0.0", + "json-buffer": "~3.0.1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 12" } }, - "node_modules/immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true - }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/concat-map": { + "version": "0.0.1", "devOptional": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", "dependencies": { - "once": "^1.3.0", - "wrappy": "1" + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" } }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "node_modules/ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "node_modules/connect": { + "version": "3.7.0", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "finalhandler": "1.1.2", + "parseurl": "~1.3.3", + "utils-merge": "1.0.1" + }, "engines": { - "node": ">= 0.10" + "node": ">= 0.10.0" } }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/connect/node_modules/debug": { + "version": "2.6.9", "dev": true, + "license": "MIT", "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" + "ms": "2.0.0" } }, - "node_modules/is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==", - "bin": { - "is-docker": "cli.js" + "node_modules/connect/node_modules/ms": { + "version": "2.0.0", + "dev": true, + "license": "MIT" + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "devOptional": true, + "license": "ISC" + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 0.6" } }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "node_modules/content-type": { + "version": "1.0.4", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/convert-source-map": { + "version": "1.9.0", "dev": true, + "license": "MIT" + }, + "node_modules/cookie": { + "version": "0.4.2", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, + "node_modules/cookie-signature": { + "version": "1.0.6", + "license": "MIT" + }, + "node_modules/copyfiles": { + "version": "2.4.1", + "dev": true, + "license": "MIT", "dependencies": { - "number-is-nan": "^1.0.0" + "glob": "^7.0.5", + "minimatch": "^3.0.3", + "mkdirp": "^1.0.4", + "noms": "0.0.0", + "through2": "^2.0.1", + "untildify": "^4.0.0", + "yargs": "^16.1.0" + }, + "bin": { + "copyfiles": "copyfiles", + "copyup": "copyfiles" + } + }, + "node_modules/copyfiles/node_modules/mkdirp": { + "version": "1.0.4", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" } }, - "node_modules/is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "node_modules/core-util-is": { + "version": "1.0.3", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.5", "dev": true, + "license": "MIT", "dependencies": { - "is-extglob": "^2.1.1" + "object-assign": "^4", + "vary": "^1" }, "engines": { - "node": ">=0.10.0" + "node": ">= 0.10" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "node_modules/create-require": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, "engines": { - "node": ">=0.12.0" + "node": ">= 8" } }, - "node_modules/is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "node_modules/custom-event": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/date-format": { + "version": "4.0.6", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4.0" } }, - "node_modules/is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", - "dev": true + "node_modules/debug": { + "version": "4.3.1", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } }, - "node_modules/is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==", + "node_modules/decamelize": { + "version": "1.2.0", + "license": "MIT", "engines": { "node": ">=0.10.0" } }, - "node_modules/is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "optional": true + "node_modules/decompress-response": { + "version": "6.0.0", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "node_modules/is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "license": "MIT", "engines": { "node": ">=10" }, @@ -3503,838 +3367,864 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", + "node_modules/deep-eql": { + "version": "3.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "is-docker": "^2.0.0" + "type-detect": "^4.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.12" } }, - "node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "node_modules/isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "license": "MIT", "engines": { - "node": ">= 8.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/gjtorikian/" + "node": ">=10" } }, - "node_modules/isexe": { + "node_modules/define-lazy-prop": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "node_modules/isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "optional": true - }, - "node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "node_modules/jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true + "node_modules/delegates": { + "version": "1.0.0", + "devOptional": true, + "license": "MIT" }, - "node_modules/json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "node_modules/depd": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "optional": true + "node_modules/deprecation": { + "version": "2.3.1", + "license": "ISC" }, - "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==", - "optional": true + "node_modules/destroy": { + "version": "1.0.4", + "license": "MIT" }, - "node_modules/json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "optional": true + "node_modules/di": { + "version": "0.0.1", + "dev": true, + "license": "MIT" }, - "node_modules/jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "node_modules/diff": { + "version": "5.0.0", "dev": true, - "dependencies": { - "universalify": "^2.0.0" - }, - "optionalDependencies": { - "graceful-fs": "^4.1.6" + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "node_modules/jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "optional": true, + "node_modules/doctrine": { + "version": "3.0.0", + "dev": true, + "license": "Apache-2.0", "dependencies": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" + "esutils": "^2.0.2" }, "engines": { - "node": ">=0.6.0" + "node": ">=6.0.0" } }, - "node_modules/jszip": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.9.1.tgz", - "integrity": "sha512-H9A60xPqJ1CuC4Ka6qxzXZeU8aNmgOeP5IFqwJbQQwtu2EUYxota3LdsiZWplF7Wgd9tkAd0mdu36nceSaPuYw==", + "node_modules/dom-serialize": { + "version": "2.2.1", "dev": true, + "license": "MIT", "dependencies": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "custom-event": "~1.0.0", + "ent": "~2.2.0", + "extend": "^3.0.0", + "void-elements": "^2.0.0" } }, - "node_modules/just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true + "node_modules/duplexer": { + "version": "0.1.2", + "dev": true, + "license": "MIT" }, - "node_modules/karma": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", - "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", + "node_modules/edge-launcher": { + "version": "1.2.2", "dev": true, - "dependencies": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" - }, - "bin": { - "karma": "bin/karma" - }, + "license": "MIT" + }, + "node_modules/ee-first": { + "version": "1.1.1", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "1.0.2", + "license": "MIT", "engines": { - "node": ">= 10" + "node": ">= 0.8" } }, - "node_modules/karma-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", - "dev": true, + "node_modules/encoding": { + "version": "0.1.13", + "license": "MIT", + "optional": true, "dependencies": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" - }, - "peerDependencies": { - "karma": ">=0.9" + "iconv-lite": "^0.6.2" } }, - "node_modules/karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", - "dev": true, - "peerDependencies": { - "chai": "*", - "karma": ">=0.10.9" + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "license": "MIT", + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "node_modules/karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, + "node_modules/end-of-stream": { + "version": "1.4.4", + "license": "MIT", "dependencies": { - "fs-access": "^1.0.0", - "which": "^1.2.1" + "once": "^1.4.0" } }, - "node_modules/karma-chrome-launcher/node_modules/which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "node_modules/engine.io": { + "version": "6.1.3", "dev": true, + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "@types/cookie": "^0.4.1", + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "accepts": "~1.3.4", + "base64id": "2.0.0", + "cookie": "~0.4.1", + "cors": "~2.8.5", + "debug": "~4.3.1", + "engine.io-parser": "~5.0.3", + "ws": "~8.2.3" }, - "bin": { - "which": "bin/which" + "engines": { + "node": ">=10.0.0" } }, - "node_modules/karma-edge-launcher": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/karma-edge-launcher/-/karma-edge-launcher-0.4.2.tgz", - "integrity": "sha512-YAJZb1fmRcxNhMIWYsjLuxwODBjh2cSHgTW/jkVmdpGguJjLbs9ZgIK/tEJsMQcBLUkO+yO4LBbqYxqgGW2HIw==", + "node_modules/engine.io-parser": { + "version": "5.0.3", "dev": true, + "license": "MIT", "dependencies": { - "edge-launcher": "1.2.2" + "@socket.io/base64-arraybuffer": "~1.0.2" }, "engines": { - "node": ">=4" - }, - "peerDependencies": { - "karma": ">=0.9" + "node": ">=10.0.0" } }, - "node_modules/karma-firefox-launcher": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz", - "integrity": "sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ==", + "node_modules/engine.io/node_modules/ws": { + "version": "8.2.3", "dev": true, - "dependencies": { - "is-wsl": "^2.1.0" + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/karma-fixture": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/karma-fixture/-/karma-fixture-0.2.6.tgz", - "integrity": "sha1-lxzqjCFtc/BwQ5ZMtz8Q4IMAGO8=", - "dev": true + "node_modules/ent": { + "version": "2.2.0", + "dev": true, + "license": "MIT" }, - "node_modules/karma-html2js-preprocessor": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/karma-html2js-preprocessor/-/karma-html2js-preprocessor-1.1.0.tgz", - "integrity": "sha1-/Ant8Eu+K7bu6boZaPgmtziAIL0=", + "node_modules/entities": { + "version": "2.1.0", "dev": true, - "peerDependencies": { - "karma": ">=0.9" + "license": "BSD-2-Clause", + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/karma-ie-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz", - "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", - "dev": true, - "dependencies": { - "lodash": "^4.6.1" - }, - "peerDependencies": { - "karma": ">=0.9" + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "optional": true, + "engines": { + "node": ">=6" } }, - "node_modules/karma-json-fixtures-preprocessor": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/karma-json-fixtures-preprocessor/-/karma-json-fixtures-preprocessor-0.0.6.tgz", - "integrity": "sha1-T3ii6800OH+OVaur/2NGVRZMTHY=", - "dev": true + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "optional": true }, - "node_modules/karma-mocha": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", - "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", + "node_modules/es6-promise": { + "version": "4.2.8", "dev": true, - "dependencies": { - "minimist": "^1.2.3" - } + "license": "MIT" }, - "node_modules/karma-mocha-reporter": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", - "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", + "node_modules/es6-promisify": { + "version": "5.0.0", "dev": true, + "license": "MIT", "dependencies": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" - }, - "peerDependencies": { - "karma": ">=0.13" + "es6-promise": "^4.0.3" } }, - "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true, - "engines": { - "node": ">=4" - } + "node_modules/es6-shim": { + "version": "0.35.6", + "license": "MIT" }, - "node_modules/karma-mocha-reporter/node_modules/ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "node_modules/esbuild": { + "version": "0.15.18", "dev": true, - "dependencies": { - "color-convert": "^1.9.0" + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" }, "engines": { - "node": ">=4" - } - }, - "node_modules/karma-mocha-reporter/node_modules/chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "dependencies": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "node": ">=12" }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.18", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": ">=12" } }, - "node_modules/karma-mocha-reporter/node_modules/color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "node_modules/escalade": { + "version": "3.1.1", "dev": true, - "dependencies": { - "color-name": "1.1.3" + "license": "MIT", + "engines": { + "node": ">=6" } }, - "node_modules/karma-mocha-reporter/node_modules/color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true + "node_modules/escape-html": { + "version": "1.0.3", + "license": "MIT" }, - "node_modules/karma-mocha-reporter/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "node_modules/escape-string-regexp": { + "version": "1.0.5", "dev": true, + "license": "MIT", "engines": { - "node": ">=4" + "node": ">=0.8.0" } }, - "node_modules/karma-mocha-reporter/node_modules/log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "node_modules/esutils": { + "version": "2.0.3", "dev": true, - "dependencies": { - "chalk": "^2.0.1" - }, + "license": "BSD-2-Clause", "engines": { - "node": ">=4" + "node": ">=0.10.0" } }, - "node_modules/karma-mocha-reporter/node_modules/strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "dependencies": { - "ansi-regex": "^3.0.0" - }, + "node_modules/etag": { + "version": "1.8.1", + "license": "MIT", "engines": { - "node": ">=4" + "node": ">= 0.6" } }, - "node_modules/karma-mocha-reporter/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "node_modules/event-stream": { + "version": "3.3.4", "dev": true, + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" + "duplexer": "~0.1.1", + "from": "~0", + "map-stream": "~0.1.0", + "pause-stream": "0.0.11", + "split": "0.3", + "stream-combiner": "~0.0.4", + "through": "~2.3.1" } }, - "node_modules/karma-safari-launcher": { + "node_modules/eventemitter3": { + "version": "4.0.7", + "license": "MIT" + }, + "node_modules/execa": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", - "integrity": "sha1-lpgqLMR9BmquccVTursoMZEVos4=", - "dev": true, - "peerDependencies": { - "karma": ">=0.9" + "license": "MIT", + "dependencies": { + "cross-spawn": "^6.0.0", + "get-stream": "^4.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "engines": { + "node": ">=6" } }, - "node_modules/karma-teamcity-reporter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/karma-teamcity-reporter/-/karma-teamcity-reporter-1.1.0.tgz", - "integrity": "sha512-Ca1uhHGtNqUuzsnW3I+QykNuS/jF9vdxnIrkbLCVJRunCc6yWJq+ai1UobQT13j0e3JVUOf0mKo3QHZ6A6mG9Q==", - "dev": true, - "peerDependencies": { - "karma": ">=0.9" + "node_modules/execa/node_modules/cross-spawn": { + "version": "6.0.5", + "license": "MIT", + "dependencies": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "engines": { + "node": ">=4.8" } }, - "node_modules/karma/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, + "node_modules/execa/node_modules/get-stream": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "pump": "^3.0.0" }, "engines": { - "node": "*" + "node": ">=6" + } + }, + "node_modules/execa/node_modules/path-key": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/execa/node_modules/semver": { + "version": "5.7.1", + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/execa/node_modules/shebang-command": { + "version": "1.2.0", + "license": "MIT", + "dependencies": { + "shebang-regex": "^1.0.0" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": ">=0.10.0" } }, - "node_modules/keyman": { - "resolved": "web", - "link": true + "node_modules/execa/node_modules/shebang-regex": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/keyv": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.2.2.tgz", - "integrity": "sha512-uYS0vKTlBIjNCAUqrjlxmruxOEiZxZIHXyp32sdcGmP+ukFrmWUnE//RcPXJH3Vxrni1H2gsQbjHE0bH7MtMQQ==", + "node_modules/execa/node_modules/which": { + "version": "1.3.1", + "license": "ISC", "dependencies": { - "compress-brotli": "^1.3.6", - "json-buffer": "3.0.1" + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" } }, - "node_modules/lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, + "node_modules/express": { + "version": "4.17.3", + "license": "MIT", "dependencies": { - "immediate": "~3.0.5" + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.19.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.4.2", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.1.2", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.9.7", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.17.2", + "serve-static": "1.14.2", + "setprototypeof": "1.2.0", + "statuses": "~1.5.0", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" } }, - "node_modules/linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dev": true, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", "dependencies": { - "uc.micro": "^1.0.1" + "ms": "2.0.0" } }, - "node_modules/locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "license": "MIT" + }, + "node_modules/extend": { + "version": "3.0.2", "dev": true, + "license": "MIT" + }, + "node_modules/extract-zip": { + "version": "2.0.1", + "license": "BSD-2-Clause", "dependencies": { - "p-locate": "^5.0.0" + "debug": "^4.1.1", + "get-stream": "^5.1.0", + "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" }, "engines": { - "node": ">=10" + "node": ">= 10.17.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "optionalDependencies": { + "@types/yauzl": "^2.9.1" } }, - "node_modules/lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true - }, - "node_modules/lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "license": "MIT" }, - "node_modules/lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + "node_modules/fd-slicer": { + "version": "1.1.0", + "license": "MIT", + "dependencies": { + "pend": "~1.2.0" + } }, - "node_modules/lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" + "node_modules/file": { + "version": "0.2.2", + "dev": true, + "license": "MIT" }, - "node_modules/lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "optional": true }, - "node_modules/log4js": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.4.tgz", - "integrity": "sha512-ncaWPsuw9Vl1CKA406hVnJLGQKy1OHx6buk8J4rE2lVW+NW5Y82G5/DIloO7NkqLOUtNPEANaWC1kZYVjXssPw==", + "node_modules/fill-range": { + "version": "7.0.1", "dev": true, + "license": "MIT", "dependencies": { - "date-format": "^4.0.6", - "debug": "^4.3.4", - "flatted": "^3.2.5", - "rfdc": "^1.3.0", - "streamroller": "^3.0.6" + "to-regex-range": "^5.0.1" }, "engines": { - "node": ">=8.0" + "node": ">=8" } }, - "node_modules/log4js/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, + "node_modules/finalhandler": { + "version": "1.1.2", + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.3", + "statuses": "~1.5.0", + "unpipe": "~1.0.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">= 0.8" } }, - "node_modules/lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", - "dev": true + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } }, - "node_modules/lowercase-keys": { + "node_modules/finalhandler/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", - "engines": { - "node": ">=8" - } + "license": "MIT" }, - "node_modules/lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" - }, - "node_modules/lru-cache": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", - "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", - "devOptional": true, - "engines": { - "node": ">=12" - } - }, - "node_modules/macos-release": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz", - "integrity": "sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g==", + "node_modules/find-up": { + "version": "5.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, "engines": { - "node": ">=6" + "node": ">=10" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "node_modules/flat": { + "version": "5.0.2", + "dev": true, + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" + } }, - "node_modules/map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true + "node_modules/flatted": { + "version": "3.2.5", + "dev": true, + "license": "ISC" }, - "node_modules/markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "node_modules/follow-redirects": { + "version": "1.14.9", "dev": true, - "dependencies": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" }, - "bin": { - "markdown-it": "bin/markdown-it.js" + "peerDependenciesMeta": { + "debug": { + "optional": true + } } }, - "node_modules/mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true + "node_modules/foreground-child": { + "version": "2.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8.0.0" + } }, - "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=", + "node_modules/forwarded": { + "version": "0.2.0", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/merge-descriptors": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "node_modules/methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=", + "node_modules/fresh": { + "version": "0.5.2", + "license": "MIT", "engines": { "node": ">= 0.6" } }, - "node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "node_modules/from": { + "version": "0.1.7", "dev": true, - "bin": { - "mime": "cli.js" + "license": "MIT" + }, + "node_modules/fs-access": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "null-check": "^1.0.0" }, "engines": { - "node": ">=4.0.0" + "node": ">=0.10.0" } }, - "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "node_modules/fs-extra": { + "version": "10.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, "engines": { - "node": ">= 0.6" + "node": ">=12" } }, - "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "optional": true, "dependencies": { - "mime-db": "1.52.0" + "minipass": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 8" } }, - "node_modules/mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", + "node_modules/fs.realpath": { + "version": "1.0.0", + "devOptional": true, + "license": "ISC" + }, + "node_modules/fsevents": { + "version": "2.3.2", + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { - "node": ">=4" + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, - "node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/function-bind": { + "version": "1.1.1", + "dev": true, + "license": "MIT" + }, + "node_modules/gauge": { + "version": "4.0.4", "devOptional": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" }, "engines": { - "node": "*" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "node_modules/gauge/node_modules/ansi-regex": { + "version": "5.0.1", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "node_modules/minipass": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", - "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", - "optional": true, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "devOptional": true, + "license": "MIT", "dependencies": { - "yallist": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" }, "engines": { "node": ">=8" } }, - "node_modules/minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "optional": true, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "6.0.1", + "devOptional": true, + "license": "MIT", "dependencies": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">= 8" + "node": ">=8" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "node_modules/gauge/node_modules/wide-align": { + "version": "1.1.5", + "devOptional": true, + "license": "ISC", "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" + "string-width": "^1.0.2 || 2 || 3 || 4" } }, - "node_modules/mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "node_modules/get-caller-file": { + "version": "2.0.5", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/get-stream": { + "version": "5.2.0", + "license": "MIT", "dependencies": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" + "pump": "^3.0.0" }, "engines": { - "node": ">= 14.0.0" + "node": ">=8" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/mochajs" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/mocha-teamcity-reporter": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mocha-teamcity-reporter/-/mocha-teamcity-reporter-4.0.0.tgz", - "integrity": "sha512-m+ADz1U7JNf3od4HrY5t4xAKIDamj1171WJCb8nrSuF+RIqP+AP4gq6UJ+U9nPVn2XO8y1nSs5bQ9Kxzelz6Nw==", + "node_modules/git-diff": { + "version": "2.0.6", "dev": true, - "engines": { - "node": ">=6" + "license": "ISC", + "dependencies": { + "chalk": "^2.3.2", + "diff": "^3.5.0", + "loglevel": "^1.6.1", + "shelljs": "^0.8.1", + "shelljs.exec": "^1.1.7" }, - "peerDependencies": { - "mocha": ">=6" + "engines": { + "node": ">= 4.8.0" } }, - "node_modules/mocha/node_modules/chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "node_modules/git-diff/node_modules/ansi-styles": { + "version": "3.2.1", "dev": true, - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "color-convert": "^1.9.0" }, "engines": { - "node": ">= 8.10.0" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" + "node": ">=4" } }, - "node_modules/mocha/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/git-diff/node_modules/chalk": { + "version": "2.4.2", "dev": true, + "license": "MIT", "dependencies": { - "ms": "2.1.2" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=4" } }, - "node_modules/mocha/node_modules/debug/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "node_modules/git-diff/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "1.1.3" + } }, - "node_modules/mocha/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==", + "node_modules/git-diff/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" + }, + "node_modules/git-diff/node_modules/diff": { + "version": "3.5.0", "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=0.3.1" } }, - "node_modules/mocha/node_modules/glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "node_modules/git-diff/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/git-diff/node_modules/supports-color": { + "version": "5.5.0", "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/glob": { + "version": "7.1.6", + "devOptional": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -4350,1326 +4240,1240 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/mocha/node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "node_modules/glob-parent": { + "version": "5.1.2", "dev": true, + "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "is-glob": "^4.0.1" }, "engines": { - "node": "*" + "node": ">= 6" } }, - "node_modules/mocha/node_modules/log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "node_modules/google-closure-compiler-java": { + "version": "20200224.0.0", "dev": true, + "license": "Apache-2.0" + }, + "node_modules/got": { + "version": "11.8.5", + "license": "MIT", "dependencies": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" + "@sindresorhus/is": "^4.0.0", + "@szmarczak/http-timer": "^4.0.5", + "@types/cacheable-request": "^6.0.1", + "@types/responselike": "^1.0.0", + "cacheable-lookup": "^5.0.3", + "cacheable-request": "^7.0.2", + "decompress-response": "^6.0.0", + "http2-wrapper": "^1.0.0-beta.5.2", + "lowercase-keys": "^2.0.0", + "p-cancelable": "^2.0.0", + "responselike": "^2.0.0" }, "engines": { - "node": ">=10" + "node": ">=10.19.0" }, "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "url": "https://github.com/sindresorhus/got?sponsor=1" } }, - "node_modules/mocha/node_modules/minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", + "node_modules/graceful-fs": { + "version": "4.2.10", + "devOptional": true, + "license": "ISC" + }, + "node_modules/growl": { + "version": "1.10.5", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", "dev": true, + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "function-bind": "^1.1.1" }, "engines": { - "node": ">=10" + "node": ">= 0.4.0" } }, - "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "node_modules/has-flag": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-unicode": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "devOptional": true, + "license": "ISC" + }, + "node_modules/he": { + "version": "1.2.0", "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "license": "MIT", + "bin": { + "he": "bin/he" } }, - "node_modules/mocha/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 + "node_modules/hetrodo-node-hide-console-window-napi": { + "name": "node-hide-console-window", + "version": "2.0.1", + "resolved": "git+ssh://git@github.com/keymanapp/hetrodo-node-hide-console-window-napi.git#858b23036a9963b40ad6ff3c5bacd421e5839b92", + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] }, - "node_modules/mocha/node_modules/nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", + "node_modules/hexy": { + "version": "0.3.4", "dev": true, + "license": "MIT", "bin": { - "nanoid": "bin/nanoid.cjs" + "hexy": "bin/hexy_cmd.js" }, "engines": { - "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + "node": ">=10.4" } }, - "node_modules/mocha/node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "node_modules/hpagent": { + "version": "0.1.2", + "license": "MIT", + "optional": true + }, + "node_modules/html-escaper": { + "version": "2.0.2", "dev": true, + "license": "MIT" + }, + "node_modules/http-cache-semantics": { + "version": "4.1.0", + "license": "BSD-2-Clause" + }, + "node_modules/http-errors": { + "version": "1.8.1", + "license": "MIT", "dependencies": { - "picomatch": "^2.2.1" + "depd": "~1.1.2", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.1" }, "engines": { - "node": ">=8.10.0" + "node": ">= 0.6" } }, - "node_modules/mocha/node_modules/serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", + "node_modules/http-proxy": { + "version": "1.18.1", "dev": true, + "license": "MIT", "dependencies": { - "randombytes": "^2.1.0" + "eventemitter3": "^4.0.0", + "follow-redirects": "^1.0.0", + "requires-port": "^1.0.0" + }, + "engines": { + "node": ">=8.0.0" } }, - "node_modules/mocha/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, + "node_modules/http-proxy-agent": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz", + "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==", + "optional": true, "dependencies": { - "has-flag": "^4.0.0" + "@tootallnate/once": "2", + "agent-base": "6", + "debug": "4" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" + "node": ">= 6" } }, - "node_modules/mocha/node_modules/workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - }, - "node_modules/modernizr": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/modernizr/-/modernizr-3.12.0.tgz", - "integrity": "sha512-i5f5xfnxMOb3cePoBpwk4bWjVAyIB3hgm7QrDvZx/R7zUUS8PO9zlyQF7vJKn8kCVxEvL0nRWeZ0PPqVbY31sw==", - "dev": true, + "node_modules/http2-wrapper": { + "version": "1.0.3", + "license": "MIT", "dependencies": { - "doctrine": "^3.0.0", - "file": "^0.2.2", - "lodash": "^4.17.21", - "markdown-it": "^12.3.2", - "mkdirp": "0.5.5", - "requirejs": "^2.3.6", - "yargs": "^15.4.1" - }, - "bin": { - "modernizr": "bin/modernizr" + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=10.19.0" } }, - "node_modules/modernizr/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, + "node_modules/https-proxy-agent": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, "engines": { - "node": ">=8" + "node": ">= 6" } }, - "node_modules/modernizr/node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "optional": true, "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" + "ms": "^2.0.0" } }, - "node_modules/modernizr/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, + "node_modules/iconv-lite": { + "version": "0.4.24", + "license": "MIT", "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" + "safer-buffer": ">= 2.1.2 < 3" }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/immediate": { + "version": "3.0.6", + "license": "MIT" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "optional": true, "engines": { "node": ">=8" } }, - "node_modules/modernizr/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "devOptional": true, + "license": "ISC", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "1.4.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">= 0.10" } }, - "node_modules/modernizr/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "node_modules/ip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ip/-/ip-2.0.0.tgz", + "integrity": "sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ==", + "optional": true + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", "dev": true, + "license": "MIT", "dependencies": { - "p-locate": "^4.1.0" + "binary-extensions": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/modernizr/node_modules/mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "node_modules/is-core-module": { + "version": "2.11.0", "dev": true, + "license": "MIT", "dependencies": { - "minimist": "^1.2.5" + "has": "^1.0.3" }, - "bin": { - "mkdirp": "bin/cmd.js" + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/modernizr/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" + "node_modules/is-docker": { + "version": "2.2.1", + "license": "MIT", + "bin": { + "is-docker": "cli.js" }, "engines": { - "node": ">=6" + "node": ">=8" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/modernizr/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "node_modules/is-extglob": { + "version": "2.1.1", "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/modernizr/node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/is-fullwidth-code-point": { + "version": "1.0.0", "dev": true, + "license": "MIT", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "number-is-nan": "^1.0.0" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/modernizr/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "node_modules/is-glob": { + "version": "4.0.1", "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^5.0.1" + "is-extglob": "^2.1.1" }, "engines": { - "node": ">=8" + "node": ">=0.10.0" } }, - "node_modules/modernizr/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "optional": true + }, + "node_modules/is-number": { + "version": "7.0.0", "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, + "license": "MIT", "engines": { - "node": ">=8" + "node": ">=0.12.0" } }, - "node_modules/modernizr/node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true + "node_modules/is-plain-object": { + "version": "5.0.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "node_modules/modernizr/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "node_modules/is-running": { + "version": "2.1.0", + "dev": true, + "license": "BSD" + }, + "node_modules/is-stream": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-wsl": { + "version": "2.2.0", + "license": "MIT", "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "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" + "is-docker": "^2.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/modernizr/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "node_modules/isarray": { + "version": "1.0.0", + "license": "MIT" + }, + "node_modules/isbinaryfile": { + "version": "4.0.10", "dev": true, - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, + "license": "MIT", "engines": { - "node": ">=6" + "node": ">= 8.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/gjtorikian/" } }, - "node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + "node_modules/isexe": { + "version": "2.0.0", + "license": "ISC" }, - "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "node_modules/istanbul-lib-coverage": { + "version": "3.2.0", + "dev": true, + "license": "BSD-3-Clause", "engines": { - "node": ">= 0.6" + "node": ">=8" } }, - "node_modules/ngrok": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ngrok/-/ngrok-4.3.1.tgz", - "integrity": "sha512-s0joO2liKYiGTVARyzL8hfLIXAZT03GDK3oJqsZK6d61Es+HCx77j8E9ysUbtkMEyvBgYmIMr8taQNsvQt4/DQ==", - "hasInstallScript": true, + "node_modules/istanbul-lib-report": { + "version": "3.0.0", + "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@types/node": "^8.10.50", - "extract-zip": "^2.0.1", - "got": "^11.5.1", - "lodash.clonedeep": "^4.5.0", - "uuid": "^7.0.0 || ^8.0.0", - "yaml": "^1.10.0" - }, - "bin": { - "ngrok": "bin/ngrok" + "istanbul-lib-coverage": "^3.0.0", + "make-dir": "^3.0.0", + "supports-color": "^7.1.0" }, "engines": { - "node": ">=10.19.0 <14 || >=14.2" - }, - "optionalDependencies": { - "hpagent": "^0.1.2" + "node": ">=8" } }, - "node_modules/ngrok/node_modules/@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==" - }, - "node_modules/nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node_modules/nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "node_modules/istanbul-reports": { + "version": "3.1.5", "dev": true, + "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" + "html-escaper": "^2.0.0", + "istanbul-lib-report": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "node_modules/nise/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/nise/node_modules/lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", + "node_modules/js-yaml": { + "version": "4.1.0", "dev": true, + "license": "MIT", "dependencies": { - "@sinonjs/commons": "^1.7.0" + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" } }, - "node_modules/nise/node_modules/path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "optional": true - }, - "node_modules/node-cleanup": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", - "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", - "dev": true + "node_modules/json-buffer": { + "version": "3.0.1", + "license": "MIT" }, - "node_modules/node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "node_modules/jsonfile": { + "version": "6.1.0", + "dev": true, + "license": "MIT", "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" + "universalify": "^2.0.0" }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "node_modules/node-gyp": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", - "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", - "optional": true, + "node_modules/jszip": { + "version": "3.9.1", + "license": "(MIT OR GPL-3.0-or-later)", "dependencies": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.3", - "nopt": "^5.0.0", - "npmlog": "^4.1.2", - "request": "^2.88.2", + "lie": "~3.3.0", + "pako": "~1.0.2", + "readable-stream": "~2.3.6", + "set-immediate-shim": "~1.0.1" + } + }, + "node_modules/just-extend": { + "version": "4.2.1", + "dev": true, + "license": "MIT" + }, + "node_modules/karma": { + "version": "6.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "@colors/colors": "1.5.0", + "body-parser": "^1.19.0", + "braces": "^3.0.2", + "chokidar": "^3.5.1", + "connect": "^3.7.0", + "di": "^0.0.1", + "dom-serialize": "^2.2.1", + "glob": "^7.1.7", + "graceful-fs": "^4.2.6", + "http-proxy": "^1.18.1", + "isbinaryfile": "^4.0.8", + "lodash": "^4.17.21", + "log4js": "^6.4.1", + "mime": "^2.5.2", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.5", + "qjobs": "^1.2.0", + "range-parser": "^1.2.1", "rimraf": "^3.0.2", - "semver": "^7.3.2", - "tar": "^6.0.2", - "which": "^2.0.2" + "socket.io": "^4.4.1", + "source-map": "^0.6.1", + "tmp": "^0.2.1", + "ua-parser-js": "^0.7.30", + "yargs": "^16.1.1" }, "bin": { - "node-gyp": "bin/node-gyp.js" + "karma": "bin/karma" }, "engines": { - "node": ">= 10.12.0" + "node": ">= 10" } }, - "node_modules/node-windows-trayicon": { - "name": "windows-trayicon", - "version": "3.0.0", - "resolved": "git+ssh://git@github.com/keymanapp/node-windows-trayicon.git#1e46786082213f3edcddd5953e33f5abdc7ea05f", - "hasInstallScript": true, + "node_modules/karma-browserstack-launcher": { + "version": "1.6.0", + "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], "dependencies": { - "bindings": "^1.5.0", - "node-addon-api": "^3.1.0", - "node-gyp": "^7.1.2" + "browserstack": "~1.5.1", + "browserstack-local": "^1.3.7", + "q": "~1.5.0" + }, + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/noms": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", - "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", + "node_modules/karma-chai": { + "version": "0.1.0", "dev": true, - "dependencies": { - "inherits": "^2.0.1", - "readable-stream": "~1.0.31" + "license": "MIT", + "peerDependencies": { + "chai": "*", + "karma": ">=0.10.9" } }, - "node_modules/noms/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "node_modules/noms/node_modules/readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", + "node_modules/karma-chrome-launcher": { + "version": "2.2.0", "dev": true, + "license": "MIT", "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "fs-access": "^1.0.0", + "which": "^1.2.1" } }, - "node_modules/noms/node_modules/string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "node_modules/nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "optional": true, + "node_modules/karma-chrome-launcher/node_modules/which": { + "version": "1.3.1", + "dev": true, + "license": "ISC", "dependencies": { - "abbrev": "1" + "isexe": "^2.0.0" }, "bin": { - "nopt": "bin/nopt.js" - }, - "engines": { - "node": ">=6" + "which": "bin/which" } }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "node_modules/karma-edge-launcher": { + "version": "0.4.2", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "license": "MIT", "dependencies": { - "path-key": "^2.0.0" + "edge-launcher": "1.2.2" }, "engines": { "node": ">=4" + }, + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/npm-run-path/node_modules/path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==", - "engines": { - "node": ">=4" + "node_modules/karma-firefox-launcher": { + "version": "1.3.0", + "dev": true, + "license": "MIT", + "dependencies": { + "is-wsl": "^2.1.0" } }, - "node_modules/npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, - "dependencies": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "node_modules/karma-fixture": { + "version": "0.2.6", + "dev": true, + "license": "MIT" + }, + "node_modules/karma-html2js-preprocessor": { + "version": "1.1.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/null-check": { + "node_modules/karma-ie-launcher": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", "dev": true, - "engines": { - "node": ">=0.10.0" + "license": "MIT", + "dependencies": { + "lodash": "^4.6.1" + }, + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true, - "engines": { - "node": ">=0.10.0" + "node_modules/karma-json-fixtures-preprocessor": { + "version": "0.0.6", + "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/karma-mocha": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.3" } }, - "node_modules/oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "optional": true, - "engines": { - "node": "*" + "node_modules/karma-mocha-reporter": { + "version": "2.2.5", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "^2.1.0", + "log-symbols": "^2.1.0", + "strip-ansi": "^4.0.0" + }, + "peerDependencies": { + "karma": ">=0.13" } }, - "node_modules/object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "node_modules/karma-mocha-reporter/node_modules/ansi-regex": { + "version": "3.0.1", + "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=4" } }, - "node_modules/octokit-pagination-methods": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", - "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==" - }, - "node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "node_modules/karma-mocha-reporter/node_modules/ansi-styles": { + "version": "3.2.1", + "dev": true, + "license": "MIT", "dependencies": { - "ee-first": "1.1.1" + "color-convert": "^1.9.0" }, "engines": { - "node": ">= 0.8" + "node": ">=4" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", + "node_modules/karma-mocha-reporter/node_modules/chalk": { + "version": "2.4.2", + "dev": true, + "license": "MIT", "dependencies": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" }, "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/os-name": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", - "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", + "node_modules/karma-mocha-reporter/node_modules/color-convert": { + "version": "1.9.3", + "dev": true, + "license": "MIT", "dependencies": { - "macos-release": "^2.2.0", - "windows-release": "^3.1.0" - }, - "engines": { - "node": ">=6" + "color-name": "1.1.3" } }, - "node_modules/p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", - "engines": { - "node": ">=8" - } + "node_modules/karma-mocha-reporter/node_modules/color-name": { + "version": "1.1.3", + "dev": true, + "license": "MIT" }, - "node_modules/p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==", + "node_modules/karma-mocha-reporter/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", "engines": { "node": ">=4" } }, - "node_modules/p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/karma-mocha-reporter/node_modules/log-symbols": { + "version": "2.2.0", "dev": true, + "license": "MIT", "dependencies": { - "yocto-queue": "^0.1.0" + "chalk": "^2.0.1" }, "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">=4" } }, - "node_modules/p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/karma-mocha-reporter/node_modules/strip-ansi": { + "version": "4.0.0", "dev": true, + "license": "MIT", "dependencies": { - "p-limit": "^3.0.2" - }, - "engines": { - "node": ">=10" + "ansi-regex": "^3.0.0" }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "engines": { - "node": ">=6" - } - }, - "node_modules/pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "engines": { - "node": ">= 0.8" + "node": ">=4" } }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "node_modules/karma-mocha-reporter/node_modules/supports-color": { + "version": "5.5.0", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, "engines": { - "node": ">=8" + "node": ">=4" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "devOptional": true, - "engines": { - "node": ">=0.10.0" + "node_modules/karma-safari-launcher": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "node_modules/karma-teamcity-reporter": { + "version": "1.1.0", "dev": true, - "engines": { - "node": ">=8" + "license": "MIT", + "peerDependencies": { + "karma": ">=0.9" } }, - "node_modules/path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" - }, - "node_modules/pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "node_modules/karma/node_modules/glob": { + "version": "7.2.0", "dev": true, + "license": "ISC", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, "engines": { "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", - "dev": true, + "node_modules/keyman": { + "resolved": "web", + "link": true + }, + "node_modules/keyv": { + "version": "4.2.2", + "license": "MIT", "dependencies": { - "through": "~2.3" + "compress-brotli": "^1.3.6", + "json-buffer": "3.0.1" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" + "node_modules/lie": { + "version": "3.3.0", + "license": "MIT", + "dependencies": { + "immediate": "~3.0.5" + } }, - "node_modules/performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "optional": true + "node_modules/linkify-it": { + "version": "3.0.3", + "dev": true, + "license": "MIT", + "dependencies": { + "uc.micro": "^1.0.1" + } }, - "node_modules/picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", + "node_modules/locate-path": { + "version": "6.0.0", "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, "engines": { - "node": ">=8.6" + "node": ">=10" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", + "node_modules/lodash": { + "version": "4.17.21", "dev": true, - "engines": { - "node": ">=0.4.0" - } + "license": "MIT" }, - "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "node_modules/lodash.clonedeep": { + "version": "4.5.0", + "license": "MIT" + }, + "node_modules/lodash.get": { + "version": "4.4.2", + "license": "MIT" + }, + "node_modules/lodash.set": { + "version": "4.3.2", + "license": "MIT" + }, + "node_modules/lodash.uniq": { + "version": "4.5.0", + "license": "MIT" + }, + "node_modules/log-symbols": { + "version": "4.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "chalk": "^4.0.0" }, "engines": { - "node": ">= 0.10" + "node": ">=10" } }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, - "node_modules/ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "node_modules/log4js": { + "version": "6.4.4", "dev": true, + "license": "Apache-2.0", "dependencies": { - "event-stream": "=3.3.4" - }, - "bin": { - "ps-tree": "bin/ps-tree.js" + "date-format": "^4.0.6", + "debug": "^4.3.4", + "flatted": "^3.2.5", + "rfdc": "^1.3.0", + "streamroller": "^3.0.6" }, "engines": { - "node": ">= 0.10" + "node": ">=8.0" } }, - "node_modules/psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "optional": true - }, - "node_modules/pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "node_modules/log4js/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "license": "MIT", "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "optional": true, + "ms": "2.1.2" + }, "engines": { - "node": ">=6" + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } } }, - "node_modules/q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", + "node_modules/loglevel": { + "version": "1.8.1", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.6.0", - "teleport": ">=0.2.0" + "node": ">= 0.6.0" + }, + "funding": { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/loglevel" } }, - "node_modules/qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", + "node_modules/lolex": { + "version": "4.2.0", "dev": true, + "license": "BSD-3-Clause" + }, + "node_modules/lowercase-keys": { + "version": "2.0.0", + "license": "MIT", "engines": { - "node": ">=0.9" + "node": ">=8" } }, - "node_modules/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==", - "engines": { - "node": ">=0.6" + "node_modules/lru_map": { + "version": "0.3.3", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" }, - "funding": { - "url": "https://github.com/sponsors/ljharb" + "engines": { + "node": ">=10" } }, - "node_modules/quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "node_modules/macos-release": { + "version": "2.5.0", + "license": "MIT", "engines": { - "node": ">=10" + "node": ">=6" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/make-dir": { + "version": "3.1.0", "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" + "semver": "^6.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "node_modules/make-dir/node_modules/semver": { + "version": "6.3.0", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" } }, - "node_modules/readable-stream/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - }, - "node_modules/readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "node_modules/make-error": { + "version": "1.3.6", "dev": true, - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } + "license": "ISC" }, - "node_modules/request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "deprecated": "request has been deprecated, see https://github.com/request/request/issues/3142", + "node_modules/make-fetch-happen": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz", + "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==", "optional": true, "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" + "agentkeepalive": "^4.2.1", + "cacache": "^16.1.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^5.0.0", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^7.7.1", + "minipass": "^3.1.6", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^2.0.3", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.3", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^7.0.0", + "ssri": "^9.0.0" }, "engines": { - "node": ">= 6" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/request/node_modules/qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "7.16.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.16.1.tgz", + "integrity": "sha512-9kkuMZHnLH/8qXARvYSjNvq8S1GYFFzynQTAfKeaJ0sIrR3PUPuu37Z+EiIANiZBvpfTf2B5y8ecDLSMWlLv+w==", "optional": true, "engines": { - "node": ">=0.6" + "node": ">=12" } }, - "node_modules/request/node_modules/uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.", - "optional": true, + "node_modules/map-stream": { + "version": "0.1.0", + "dev": true + }, + "node_modules/markdown-it": { + "version": "12.3.2", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1", + "entities": "~2.1.0", + "linkify-it": "^3.0.1", + "mdurl": "^1.0.1", + "uc.micro": "^1.0.5" + }, "bin": { - "uuid": "bin/uuid" + "markdown-it": "bin/markdown-it.js" } }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", + "node_modules/mdurl": { + "version": "1.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/media-typer": { + "version": "0.3.0", + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">= 0.6" } }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "node_modules/merge-descriptors": { + "version": "1.0.1", + "license": "MIT" }, - "node_modules/requirejs": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", - "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "node_modules/methods": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "2.6.0", "dev": true, + "license": "MIT", "bin": { - "r_js": "bin/r.js", - "r.js": "bin/r.js" + "mime": "cli.js" }, "engines": { - "node": ">=0.4.0" + "node": ">=4.0.0" } }, - "node_modules/requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true - }, - "node_modules/resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } }, - "node_modules/responselike": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", - "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", + "node_modules/mime-types": { + "version": "2.1.35", + "license": "MIT", "dependencies": { - "lowercase-keys": "^2.0.0" + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" } }, - "node_modules/rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true + "node_modules/mimic-response": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "node_modules/minimatch": { + "version": "3.1.2", "devOptional": true, + "license": "ISC", "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" + "brace-expansion": "^1.1.7" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "engines": { + "node": "*" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "node_modules/sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "node_modules/minimist": { + "version": "1.2.6", + "license": "MIT" }, - "node_modules/semver": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", - "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", - "devOptional": true, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "optional": true, "dependencies": { - "lru-cache": "^7.4.0" - }, - "bin": { - "semver": "bin/semver.js" + "yallist": "^4.0.0" }, "engines": { - "node": "^10.0.0 || ^12.0.0 || ^14.0.0 || >=16.0.0" + "node": ">=8" } }, - "node_modules/send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "optional": true, "dependencies": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "1.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "minipass": "^3.0.0" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 8" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "node_modules/minipass-fetch": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz", + "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==", + "optional": true, "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - }, - "node_modules/send/node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", - "bin": { - "mime": "cli.js" + "minipass": "^3.1.6", + "minipass-sized": "^1.0.3", + "minizlib": "^2.1.2" }, "engines": { - "node": ">=4" + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + }, + "optionalDependencies": { + "encoding": "^0.1.13" } }, - "node_modules/send/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==" - }, - "node_modules/serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "optional": true, "dependencies": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" + "minipass": "^3.0.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "node_modules/set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true, - "engines": { - "node": ">=0.10.0" + "node": ">= 8" } }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" - }, - "node_modules/shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "dev": true, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "optional": true, "dependencies": { - "shebang-regex": "^3.0.0" + "minipass": "^3.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, "engines": { "node": ">=8" } }, - "node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" - }, - "node_modules/sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", - "dev": true, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "optional": true, "dependencies": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/sinon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, "engines": { - "node": ">=4" + "node": ">= 8" } }, - "node_modules/sinon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, + "node_modules/mkdirp": { + "version": "0.5.6", + "license": "MIT", "dependencies": { - "has-flag": "^3.0.0" + "minimist": "^1.2.6" }, - "engines": { - "node": ">=4" + "bin": { + "mkdirp": "bin/cmd.js" } }, - "node_modules/socket.io": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.0.tgz", - "integrity": "sha512-b65bp6INPk/BMMrIgVvX12x3Q+NqlGqSlTuvKQWt0BUJ3Hyy3JangBl7fEoWZTXbOKlCqNPbQ6MbWgok/km28w==", + "node_modules/mocha": { + "version": "10.0.0", "dev": true, + "license": "MIT", "dependencies": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.4.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.1" + "@ungap/promise-all-settled": "1.1.2", + "ansi-colors": "4.1.1", + "browser-stdout": "1.3.1", + "chokidar": "3.5.3", + "debug": "4.3.4", + "diff": "5.0.0", + "escape-string-regexp": "4.0.0", + "find-up": "5.0.0", + "glob": "7.2.0", + "he": "1.2.0", + "js-yaml": "4.1.0", + "log-symbols": "4.1.0", + "minimatch": "5.0.1", + "ms": "2.1.3", + "nanoid": "3.3.3", + "serialize-javascript": "6.0.0", + "strip-json-comments": "3.1.1", + "supports-color": "8.1.1", + "workerpool": "6.2.1", + "yargs": "16.2.0", + "yargs-parser": "20.2.4", + "yargs-unparser": "2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, "engines": { - "node": ">=10.0.0" + "node": ">= 14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mochajs" } }, - "node_modules/socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", + "node_modules/mocha-teamcity-reporter": { + "version": "4.0.0", "dev": true, - "dependencies": { - "ws": "~8.11.0" + "license": "MIT", + "engines": { + "node": ">=6" + }, + "peerDependencies": { + "mocha": ">=6" } }, - "node_modules/socket.io-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", - "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", + "node_modules/mocha/node_modules/chokidar": { + "version": "3.5.3", "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "license": "MIT", "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" }, "engines": { - "node": ">=10.0.0" + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" } }, - "node_modules/socket.io/node_modules/debug": { + "node_modules/mocha/node_modules/debug": { "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "dev": true, + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -5682,6413 +5486,2464 @@ } } }, - "node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "node_modules/mocha/node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "dev": true, + "license": "MIT" + }, + "node_modules/mocha/node_modules/escape-string-regexp": { + "version": "4.0.0", "dev": true, + "license": "MIT", "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "node_modules/mocha/node_modules/glob": { + "version": "7.2.0", "dev": true, + "license": "ISC", "dependencies": { - "through": "2" + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" }, "engines": { "node": "*" - } - }, - "node_modules/sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "optional": true, - "dependencies": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - }, - "bin": { - "sshpk-conv": "bin/sshpk-conv", - "sshpk-sign": "bin/sshpk-sign", - "sshpk-verify": "bin/sshpk-verify" }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=", - "engines": { - "node": ">= 0.6" + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", + "node_modules/mocha/node_modules/glob/node_modules/minimatch": { + "version": "3.1.2", "dev": true, + "license": "ISC", "dependencies": { - "duplexer": "~0.1.1" + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "node_modules/streamroller": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.6.tgz", - "integrity": "sha512-Qz32plKq/MZywYyhEatxyYc8vs994Gz0Hu2MSYXXLD233UyPeIeRBZARIIGwFer4Mdb8r3Y2UqKkgyDghM6QCg==", + "node_modules/mocha/node_modules/log-symbols": { + "version": "4.1.0", "dev": true, + "license": "MIT", "dependencies": { - "date-format": "^4.0.6", - "debug": "^4.3.4", - "fs-extra": "^10.0.1" + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" }, "engines": { - "node": ">=8.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/streamroller/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "node_modules/mocha/node_modules/minimatch": { + "version": "5.0.1", "dev": true, + "license": "ISC", "dependencies": { - "ms": "2.1.2" + "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } + "node": ">=10" } }, - "node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "node_modules/mocha/node_modules/minimatch/node_modules/brace-expansion": { + "version": "2.0.1", + "dev": true, + "license": "MIT", "dependencies": { - "safe-buffer": "~5.1.0" + "balanced-match": "^1.0.0" } }, - "node_modules/string_decoder/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "node_modules/mocha/node_modules/ms": { + "version": "2.1.3", + "dev": true, + "license": "MIT" }, - "node_modules/string-argv": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", - "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", + "node_modules/mocha/node_modules/nanoid": { + "version": "3.3.3", "dev": true, + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, "engines": { - "node": ">=0.6.19" + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "node_modules/string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, + "node_modules/mocha/node_modules/readdirp": { + "version": "3.6.0", + "dev": true, + "license": "MIT", "dependencies": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "picomatch": "^2.2.1" }, "engines": { - "node": ">=0.10.0" + "node": ">=8.10.0" } }, - "node_modules/string.prototype.codepointat": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" - }, - "node_modules/string.prototype.startswith": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-0.2.0.tgz", - "integrity": "sha1-2miYLjU6TprEpDtFCiBF0cRFrns=" + "node_modules/mocha/node_modules/serialize-javascript": { + "version": "6.0.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" + } }, - "node_modules/strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, + "node_modules/mocha/node_modules/supports-color": { + "version": "8.1.1", + "dev": true, + "license": "MIT", "dependencies": { - "ansi-regex": "^2.0.0" + "has-flag": "^4.0.0" }, "engines": { - "node": ">=0.10.0" + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==", + "node_modules/mocha/node_modules/workerpool": { + "version": "6.2.1", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/modernizr": { + "version": "3.12.0", + "dev": true, + "license": "MIT", + "dependencies": { + "doctrine": "^3.0.0", + "file": "^0.2.2", + "lodash": "^4.17.21", + "markdown-it": "^12.3.2", + "mkdirp": "0.5.5", + "requirejs": "^2.3.6", + "yargs": "^15.4.1" + }, + "bin": { + "modernizr": "bin/modernizr" + }, "engines": { - "node": ">=0.10.0" + "node": ">=8" } }, - "node_modules/strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "node_modules/modernizr/node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, + "license": "MIT", "engines": { "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "node_modules/modernizr/node_modules/cliui": { + "version": "6.0.0", + "dev": true, + "license": "ISC", "dependencies": { - "has-flag": "^4.0.0" + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/modernizr/node_modules/find-up": { + "version": "4.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" }, "engines": { "node": ">=8" } }, - "node_modules/tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "optional": true, + "node_modules/modernizr/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/modernizr/node_modules/locate-path": { + "version": "5.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" + "p-locate": "^4.1.0" }, "engines": { - "node": ">= 10" + "node": ">=8" } }, - "node_modules/tar/node_modules/mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true, + "node_modules/modernizr/node_modules/mkdirp": { + "version": "0.5.5", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5" + }, "bin": { "mkdirp": "bin/cmd.js" - }, - "engines": { - "node": ">=10" } }, - "node_modules/temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha1-gHFzBDeHByDpQxUy/igUNk+IA9c=", + "node_modules/modernizr/node_modules/p-limit": { + "version": "2.3.0", "dev": true, + "license": "MIT", "dependencies": { - "rimraf": "~2.5.2" + "p-try": "^2.0.0" }, "engines": { - "node": ">=0.8.0" + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/temp-fs/node_modules/rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", + "node_modules/modernizr/node_modules/p-locate": { + "version": "4.1.0", "dev": true, + "license": "MIT", "dependencies": { - "glob": "^7.0.5" + "p-limit": "^2.2.0" }, - "bin": { - "rimraf": "bin.js" + "engines": { + "node": ">=8" } }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "node_modules/through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/modernizr/node_modules/string-width": { + "version": "4.2.3", "dev": true, + "license": "MIT", "dependencies": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "node_modules/tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "node_modules/modernizr/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, + "license": "MIT", "dependencies": { - "rimraf": "^3.0.0" + "ansi-regex": "^5.0.1" }, "engines": { - "node": ">=8.17.0" + "node": ">=8" } }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/modernizr/node_modules/wrap-ansi": { + "version": "6.2.0", "dev": true, + "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" }, "engines": { - "node": ">=8.0" + "node": ">=8" } }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "node_modules/modernizr/node_modules/y18n": { + "version": "4.0.3", + "dev": true, + "license": "ISC" + }, + "node_modules/modernizr/node_modules/yargs": { + "version": "15.4.1", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "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" + }, "engines": { - "node": ">=0.6" + "node": ">=8" } }, - "node_modules/tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, + "node_modules/modernizr/node_modules/yargs-parser": { + "version": "18.1.3", + "dev": true, + "license": "ISC", "dependencies": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" }, "engines": { - "node": ">=0.8" + "node": ">=6" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" + "node_modules/ms": { + "version": "2.1.2", + "license": "MIT" }, - "node_modules/ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "node_modules/nanoid": { + "version": "3.1.20", "dev": true, - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, + "license": "MIT", "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" + "nanoid": "bin/nanoid.cjs" }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" } }, - "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, + "node_modules/negotiator": { + "version": "0.6.3", + "license": "MIT", "engines": { - "node": ">=0.3.1" + "node": ">= 0.6" } }, - "node_modules/tsc-watch": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.6.2.tgz", - "integrity": "sha512-eHWzZGkPmzXVGQKbqQgf3BFpGiZZw1jQ29ZOJeaSe8JfyUvphbd221NfXmmsJUGGPGA/nnaSS01tXipUcyxAxg==", - "dev": true, + "node_modules/ngrok": { + "version": "4.3.1", + "hasInstallScript": true, + "license": "BSD-2-Clause", "dependencies": { - "cross-spawn": "^7.0.3", - "node-cleanup": "^2.1.2", - "ps-tree": "^1.2.0", - "string-argv": "^0.1.1", - "strip-ansi": "^6.0.0" + "@types/node": "^8.10.50", + "extract-zip": "^2.0.1", + "got": "^11.5.1", + "lodash.clonedeep": "^4.5.0", + "uuid": "^7.0.0 || ^8.0.0", + "yaml": "^1.10.0" }, "bin": { - "tsc-watch": "index.js" - }, - "engines": { - "node": ">=8.17.0" + "ngrok": "bin/ngrok" }, - "peerDependencies": { - "typescript": "*" - } - }, - "node_modules/tsc-watch/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, "engines": { - "node": ">=8" - } - }, - "node_modules/tsc-watch/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.1" + "node": ">=10.19.0 <14 || >=14.2" }, - "engines": { - "node": ">=8" - } - }, - "node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "node_modules/tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==", - "engines": { - "node": ">=0.6.11 <=0.7.0 || >=0.7.3" + "optionalDependencies": { + "hpagent": "^0.1.2" } }, - "node_modules/tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "optional": true, - "dependencies": { - "safe-buffer": "^5.0.1" - }, - "engines": { - "node": "*" - } + "node_modules/ngrok/node_modules/@types/node": { + "version": "8.10.66", + "license": "MIT" }, - "node_modules/tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "node_modules/nice-try": { + "version": "1.0.5", + "license": "MIT" }, - "node_modules/type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "node_modules/nise": { + "version": "1.5.3", "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "BSD-3-Clause", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "node_modules/typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=4.2.0" - } - }, - "node_modules/ua-parser-js": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", - "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/ua-parser-js" - }, - { - "type": "paypal", - "url": "https://paypal.me/faisalman" - } - ], - "engines": { - "node": "*" + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "lolex": "^5.0.1", + "path-to-regexp": "^1.7.0" } }, - "node_modules/uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true - }, - "node_modules/universal-user-agent": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" - }, - "node_modules/universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "node_modules/nise/node_modules/isarray": { + "version": "0.0.1", "dev": true, - "engines": { - "node": ">= 10.0.0" - } - }, - "node_modules/unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=", - "engines": { - "node": ">= 0.8" - } + "license": "MIT" }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", + "node_modules/nise/node_modules/lolex": { + "version": "5.1.2", "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "optional": true, + "license": "BSD-3-Clause", "dependencies": { - "punycode": "^2.1.0" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "engines": { - "node": ">= 0.4.0" - } - }, - "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "node_modules/vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", - "engines": { - "node": ">= 0.8" + "@sinonjs/commons": "^1.7.0" } }, - "node_modules/verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "engines": [ - "node >=0.6.0" - ], - "optional": true, + "node_modules/nise/node_modules/path-to-regexp": { + "version": "1.8.0", + "dev": true, + "license": "MIT", "dependencies": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "isarray": "0.0.1" } }, - "node_modules/verror/node_modules/core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", "optional": true }, - "node_modules/void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", + "node_modules/node-cleanup": { + "version": "2.1.2", "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" - }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } + "license": "MIT" }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, + "node_modules/node-fetch": { + "version": "2.6.7", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" - }, - "bin": { - "node-which": "bin/node-which" + "whatwg-url": "^5.0.0" }, "engines": { - "node": ">= 8" - } - }, - "node_modules/which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" - }, - "node_modules/wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "optional": true, - "dependencies": { - "string-width": "^1.0.2 || 2" - } - }, - "node_modules/windows-release": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.3.tgz", - "integrity": "sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==", - "dependencies": { - "execa": "^1.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/wrap-ansi?sponsor=1" - } - }, - "node_modules/wrap-ansi/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrap-ansi/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "node_modules/ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "engines": { - "node": ">=10.0.0" + "node": "4.x || >=6.0.0" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": "^5.0.2" + "encoding": "^0.1.0" }, "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, - "node_modules/xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "dependencies": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "engines": { - "node": ">= 6" - } - }, - "node_modules/yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "dependencies": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", - "require-directory": "^2.1.1", - "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "dependencies": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/yargs-unparser/node_modules/camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/yargs-unparser/node_modules/is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs/node_modules/strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "dependencies": { - "ansi-regex": "^5.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "resources/build/version": { - "name": "@keymanapp/auto-history-action", - "license": "MIT", - "dependencies": { - "@actions/core": "^1.9.1", - "@actions/github": "^2.1.0", - "typescript": "^4.9.5", - "yargs": "^15.1.0" - }, - "devDependencies": { - "@types/node": "^13.7.0", - "@types/semver": "^7.1.0", - "semver": "^7.1.2", - "ts-node": "^10.9.1" - }, - "engines": { - "node": ">=16.0" - } - }, - "resources/build/version/node_modules/@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==", - "dev": true - }, - "resources/build/version/node_modules/ansi-regex": { - "version": "5.0.1", - "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", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "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", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/is-fullwidth-code-point": { - "version": "3.0.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/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "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", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/string-width": { - "version": "4.2.3", - "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", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "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", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "resources/build/version/node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "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" - }, - "engines": { - "node": ">=8" - } - }, - "resources/build/version/node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "resources/gosh": { - "name": "@keymanapp/resources-gosh", - "license": "MIT", - "bin": { - "gosh": "gosh.js" - } - }, - "web": { - "name": "keyman", - "license": "MIT", - "dependencies": { - "@keymanapp/input-processor": "*", - "@keymanapp/keyboard-processor": "*", - "@keymanapp/keyman-version": "*", - "@keymanapp/lexical-model-layer": "*", - "@keymanapp/models-types": "*", - "@keymanapp/recorder-core": "*", - "@keymanapp/web-utils": "*", - "@types/node": "^11.9.4", - "eventemitter3": "^4.0.0" - }, - "devDependencies": { - "@keymanapp/resources-gosh": "*", - "@keymanapp/web-sentry-manager": "*", - "@sentry/cli": "2.2.0", - "chai": "^4.3.4", - "google-closure-compiler-java": "^20200224.0.0", - "karma": "^6.4.1", - "karma-browserstack-launcher": "^1.6.0", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.1.0", - "karma-fixture": "^0.2.6", - "karma-html2js-preprocessor": "^1.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-json-fixtures-preprocessor": "0.0.6", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-safari-launcher": "^1.0.0", - "karma-teamcity-reporter": "^1.1.0", - "mocha": "^10.0.0", - "modernizr": "^3.11.7", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - } - } - }, - "dependencies": { - "@actions/core": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/@actions/core/-/core-1.9.1.tgz", - "integrity": "sha512-5ad+U2YGrmmiw6du20AQW5XuWo7UKN2052FjSV7MX+Wfjf8sCqcsZe62NfgHys4QI4/Y+vQvLKYL8jWtA1ZBTA==", - "requires": { - "@actions/http-client": "^2.0.1", - "uuid": "^8.3.2" - } - }, - "@actions/github": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@actions/github/-/github-2.2.0.tgz", - "integrity": "sha512-9UAZqn8ywdR70n3GwVle4N8ALosQs4z50N7XMXrSTUVOmVpaBC5kE3TRTT7qQdi3OaQV24mjGuJZsHUmhD+ZXw==", - "requires": { - "@actions/http-client": "^1.0.3", - "@octokit/graphql": "^4.3.1", - "@octokit/rest": "^16.43.1" - }, - "dependencies": { - "@actions/http-client": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-1.0.11.tgz", - "integrity": "sha512-VRYHGQV1rqnROJqdMvGUbY/Kn8vriQe/F9HR2AlYHzmKuM/p3kjNuXhmdBfcVgsvRWTz5C5XW5xvndZrVBuAYg==", - "requires": { - "tunnel": "0.0.6" - } - } - } - }, - "@actions/http-client": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@actions/http-client/-/http-client-2.0.1.tgz", - "integrity": "sha512-PIXiMVtz6VvyaRsGY268qvj57hXQEpsYogYOu2nrQhlf+XCGmZstmuZBbAybUl1nQGnvS1k1eEsQ69ZoD7xlSw==", - "requires": { - "tunnel": "^0.0.6" - } - }, - "@colors/colors": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", - "dev": true - }, - "@cspotcode/source-map-support": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz", - "integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==", - "dev": true, - "requires": { - "@jridgewell/trace-mapping": "0.3.9" - } - }, - "@jridgewell/resolve-uri": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.0.7.tgz", - "integrity": "sha512-8cXDaBBHOr2pQ7j77Y6Vp5VDT2sIqWyWQ56TjEq4ih/a4iST3dItRe8Q9fp0rrIl9DoKhWQtUQz/YpOxLkXbNA==", - "dev": true - }, - "@jridgewell/sourcemap-codec": { - "version": "1.4.13", - "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.13.tgz", - "integrity": "sha512-GryiOJmNcWbovBxTfZSF71V/mXbgcV3MewDe3kIMCLyIh5e7SKAeUZs+rMnJ8jkMolZ/4/VsdBmMrw3l+VdZ3w==", - "dev": true - }, - "@jridgewell/trace-mapping": { - "version": "0.3.9", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz", - "integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==", - "dev": true, - "requires": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, - "@keymanapp/auto-history-action": { - "version": "file:resources/build/version", - "requires": { - "@actions/core": "^1.9.1", - "@actions/github": "^2.1.0", - "@types/node": "^13.7.0", - "@types/semver": "^7.1.0", - "semver": "^7.1.2", - "ts-node": "^10.9.1", - "typescript": "^4.9.5", - "yargs": "^15.1.0" - }, - "dependencies": { - "@types/node": { - "version": "13.13.52", - "resolved": "https://registry.npmjs.org/@types/node/-/node-13.13.52.tgz", - "integrity": "sha512-s3nugnZumCC//n4moGGe6tkNMyYEdaDBitVjwPxXmR5lnMG5dHePinH2EdxkG3Rh1ghFHHixAG4NJhpJW1rthQ==", - "dev": true - }, - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "requires": { - "p-locate": "^4.1.0" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "requires": { - "p-limit": "^2.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "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" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } - } - }, - "@keymanapp/developer-server": { - "version": "file:developer/src/server", - "requires": { - "@keymanapp/keyman-version": "*", - "@keymanapp/resources-gosh": "*", - "@sentry/node": "^6.16.1", - "@types/chai": "^4.3.0", - "@types/express": "^4.17.13", - "@types/mocha": "^9.1.0", - "@types/multer": "^1.4.7", - "@types/node": "^17.0.0", - "@types/ws": "^8.2.2", - "chai": "^4.3.4", - "chalk": "^4.1.2", - "copyfiles": "^2.4.1", - "express": "^4.17.2", - "hetrodo-node-hide-console-window-napi": "keymanapp/hetrodo-node-hide-console-window-napi#keyman-15.0", - "mocha": "^10.0.0", - "multer": "^1.4.5-lts.1", - "ngrok": "^4.2.2", - "node-windows-trayicon": "keymanapp/node-windows-trayicon#keyman-16.0", - "open": "^8.4.0", - "ts-node": "^10.9.1", - "tsc-watch": "^4.5.0", - "typescript": "^4.9.5", - "ws": "^8.3.0" - }, - "dependencies": { - "@types/mocha": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-9.1.1.tgz", - "integrity": "sha512-Z61JK7DKDtdKTWwLeElSEBcWGRLY8g95ic5FoQqI9CMx0ns/Ghep3B4DfcEimiKMvtamNVULVNKEsiwV3aQmXw==", - "dev": true - }, - "@types/node": { - "version": "17.0.45", - "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", - "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==", - "dev": true - }, - "busboy": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", - "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", - "requires": { - "streamsearch": "^1.1.0" - } - }, - "multer": { - "version": "1.4.5-lts.1", - "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.1.tgz", - "integrity": "sha512-ywPWvcDMeH+z9gQq5qYHCCy+ethsk4goepZ45GLD63fOu0YcNecQxi64nDs3qluZB+murG3/D4dJ7+dGctcCQQ==", - "requires": { - "append-field": "^1.0.0", - "busboy": "^1.0.0", - "concat-stream": "^1.5.2", - "mkdirp": "^0.5.4", - "object-assign": "^4.1.1", - "type-is": "^1.6.4", - "xtend": "^4.0.0" - } - }, - "streamsearch": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", - "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==" - } - } - }, - "@keymanapp/input-processor": { - "version": "file:common/web/input-processor", - "requires": { - "@keymanapp/keyboard-processor": "*", - "@keymanapp/keyman-version": "*", - "@keymanapp/lexical-model-compiler": "*", - "@keymanapp/lexical-model-layer": "*", - "@keymanapp/models-types": "*", - "@keymanapp/resources-gosh": "*", - "@keymanapp/web-utils": "*", - "@types/node": "^11.9.4", - "chai": "^4.3.4", - "eventemitter3": "^4.0.0", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - } - }, - "@keymanapp/keyboard-processor": { - "version": "file:common/web/keyboard-processor", - "requires": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-types": "*", - "@keymanapp/resources-gosh": "*", - "@keymanapp/web-utils": "*", - "@types/node": "^11.9.4", - "chai": "^4.3.4", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - } - }, - "@keymanapp/keyman-version": { - "version": "file:common/web/keyman-version", - "requires": { - "typescript": "^4.9.5" - } - }, - "@keymanapp/lexical-model-compiler": { - "version": "file:developer/src/kmlmc", - "requires": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-templates": "*", - "@keymanapp/models-types": "*", - "@keymanapp/models-wordbreakers": "*", - "@types/chai": "^4.1.7", - "@types/mocha": "^5.2.7", - "@types/node": "^10.14.6", - "@types/xml2js": "^0.4.5", - "chai": "^4.3.4", - "chalk": "^2.4.2", - "commander": "^3.0.0", - "jszip": "^3.7.0", - "mocha": "^10.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5", - "xml2js": "^0.4.19" - }, - "dependencies": { - "@types/mocha": { - "version": "5.2.7", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", - "integrity": "sha512-NYrtPht0wGzhwe9+/idPaBB+TqkY9AhTvOLMkThm0IoEfLaiVQZwBwyJ5puCkO3AUCWrmcoePjp2mbFocKy4SQ==", - "dev": true - }, - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@keymanapp/lexical-model-layer": { - "version": "file:common/predictive-text", - "requires": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-templates": "*", - "@keymanapp/models-types": "*", - "@keymanapp/models-wordbreakers": "*", - "@keymanapp/resources-gosh": "*", - "@keymanapp/web-utils": "*", - "@types/node": "^10.17.21", - "chai": "^4.3.4", - "es6-shim": "^0.35.5", - "karma": "^6.4.1", - "karma-browserstack-launcher": "^1.6.0", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.1.0", - "karma-fixture": "^0.2.6", - "karma-html2js-preprocessor": "^1.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-json-fixtures-preprocessor": "0.0.6", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-safari-launcher": "^1.0.0", - "karma-teamcity-reporter": "^1.1.0", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", - "string.prototype.codepointat": "^0.2.1", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - } - } - }, - "@keymanapp/lm-message-types": { - "version": "file:common/web/lm-message-types", - "requires": { - "typescript": "^4.9.5" - } - }, - "@keymanapp/lm-worker": { - "version": "file:common/web/lm-worker", - "requires": { - "@keymanapp/keyman-version": "*", - "@keymanapp/models-templates": "*", - "@keymanapp/models-types": "*", - "@keymanapp/models-wordbreakers": "*", - "@keymanapp/resources-gosh": "*", - "@keymanapp/web-utils": "*", - "@types/node": "^10.17.21", - "chai": "^4.3.4", - "es6-shim": "^0.35.5", - "karma": "^6.4.1", - "karma-browserstack-launcher": "^1.6.0", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.1.0", - "karma-fixture": "^0.2.6", - "karma-html2js-preprocessor": "^1.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-json-fixtures-preprocessor": "0.0.6", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-safari-launcher": "^1.0.0", - "karma-teamcity-reporter": "^1.1.0", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", - "string.prototype.codepointat": "^0.2.1", - "string.prototype.startswith": "^0.2.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - } - } - }, - "@keymanapp/models-templates": { - "version": "file:common/models/templates", - "requires": { - "@keymanapp/models-types": "*", - "@keymanapp/models-wordbreakers": "*", - "@keymanapp/web-utils": "*", - "@types/chai": "^4.2.11", - "@types/mocha": "^7.0.2", - "@types/node": "^14.0.4", - "chai": "^4.3.4", - "mocha": "^10.0.0", - "typescript": "^4.9.5" - }, - "dependencies": { - "@types/node": { - "version": "14.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==", - "dev": true - } - } - }, - "@keymanapp/models-types": { - "version": "file:common/models/types", - "requires": { - "typescript": "^4.9.5" - } - }, - "@keymanapp/models-wordbreakers": { - "version": "file:common/models/wordbreakers", - "requires": { - "@keymanapp/models-types": "*", - "@types/chai": "^4.2.11", - "@types/mocha": "^7.0.2", - "@types/node": "^14.0.3", - "chai": "^4.3.4", - "mocha": "^10.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - }, - "dependencies": { - "@types/node": { - "version": "14.18.12", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.12.tgz", - "integrity": "sha512-q4jlIR71hUpWTnGhXWcakgkZeHa3CCjcQcnuzU8M891BAWA2jHiziiWEPEkdS5pFsz7H9HJiy8BrK7tBRNrY7A==" - } - } - }, - "@keymanapp/recorder-core": { - "version": "file:common/web/recorder", - "requires": { - "@keymanapp/keyboard-processor": "*", - "@keymanapp/keyman-version": "*", - "@keymanapp/models-types": "*", - "@keymanapp/web-utils": "*", - "@types/node": "^11.9.4", - "typescript": "^4.9.5" - } - }, - "@keymanapp/resources-gosh": { - "version": "file:resources/gosh" - }, - "@keymanapp/sourcemap-path-remapper": { - "version": "file:common/tools/sourcemap-path-remapper", - "requires": { - "@keymanapp/resources-gosh": "*", - "@types/node": "^10.17.21", - "chai": "^4.3.4", - "convert-source-map": "^2.0.0", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" - }, - "dependencies": { - "@types/node": { - "version": "10.17.60", - "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", - "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", - "dev": true - } - } - }, - "@keymanapp/web-sentry-manager": { - "version": "file:common/web/sentry-manager", - "requires": { - "@keymanapp/keyman-version": "*", - "@sentry/browser": "^5.27.4", - "typescript": "^4.9.5" - } - }, - "@keymanapp/web-utils": { - "version": "file:common/web/utils", - "requires": { - "@keymanapp/keyman-version": "*", - "@keymanapp/resources-gosh": "*", - "@types/node": "^14.0.5", - "typescript": "^4.9.5" - }, - "dependencies": { - "@types/node": { - "version": "14.18.18", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.18.18.tgz", - "integrity": "sha512-B9EoJFjhqcQ9OmQrNorItO+OwEOORNn3S31WuiHvZY/dm9ajkB7AKD/8toessEtHHNL+58jofbq7hMMY9v4yig==", - "dev": true - } - } - }, - "@octokit/auth-token": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@octokit/auth-token/-/auth-token-2.5.0.tgz", - "integrity": "sha512-r5FVUJCOLl19AxiuZD2VRZ/ORjp/4IN98Of6YJoJOkY75CIBuYfmiNHGrDwXr+aLGG55igl9QrxX3hbiXlLb+g==", - "requires": { - "@octokit/types": "^6.0.3" - } - }, - "@octokit/core": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/@octokit/core/-/core-3.6.0.tgz", - "integrity": "sha512-7RKRKuA4xTjMhY+eG3jthb3hlZCsOwg3rztWh75Xc+ShDWOfDDATWbeZpAHBNRpm4Tv9WgBMOy1zEJYXG6NJ7Q==", - "peer": true, - "requires": { - "@octokit/auth-token": "^2.4.4", - "@octokit/graphql": "^4.5.8", - "@octokit/request": "^5.6.3", - "@octokit/request-error": "^2.0.5", - "@octokit/types": "^6.0.3", - "before-after-hook": "^2.2.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/endpoint": { - "version": "6.0.12", - "resolved": "https://registry.npmjs.org/@octokit/endpoint/-/endpoint-6.0.12.tgz", - "integrity": "sha512-lF3puPwkQWGfkMClXb4k/eUT/nZKQfxinRWJrdZaJO85Dqwo/G0yOC434Jr2ojwafWJMYqFGFa5ms4jJUgujdA==", - "requires": { - "@octokit/types": "^6.0.3", - "is-plain-object": "^5.0.0", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/graphql": { - "version": "4.8.0", - "resolved": "https://registry.npmjs.org/@octokit/graphql/-/graphql-4.8.0.tgz", - "integrity": "sha512-0gv+qLSBLKF0z8TKaSKTsS39scVKF9dbMxJpj3U0vC7wjNWFuIpL/z76Qe2fiuCbDRcJSavkXsVtMS6/dtQQsg==", - "requires": { - "@octokit/request": "^5.6.0", - "@octokit/types": "^6.0.3", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/openapi-types": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/@octokit/openapi-types/-/openapi-types-11.2.0.tgz", - "integrity": "sha512-PBsVO+15KSlGmiI8QAzaqvsNlZlrDlyAJYcrXBCvVUxCp7VnXjkwPoFHgjEJXx3WF9BAwkA6nfCUA7i9sODzKA==" - }, - "@octokit/plugin-paginate-rest": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-1.1.2.tgz", - "integrity": "sha512-jbsSoi5Q1pj63sC16XIUboklNw+8tL9VOnJsWycWYR78TKss5PVpIPb1TUUcMQ+bBh7cY579cVAWmf5qG+dw+Q==", - "requires": { - "@octokit/types": "^2.0.1" - }, - "dependencies": { - "@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", - "requires": { - "@types/node": ">= 8" - } - } - } - }, - "@octokit/plugin-request-log": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@octokit/plugin-request-log/-/plugin-request-log-1.0.4.tgz", - "integrity": "sha512-mLUsMkgP7K/cnFEw07kWqXGF5LKrOkD+lhCrKvPHXWDywAwuDUeDwWBpc69XK3pNX0uKiVt8g5z96PJ6z9xCFA==", - "requires": {} - }, - "@octokit/plugin-rest-endpoint-methods": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-2.4.0.tgz", - "integrity": "sha512-EZi/AWhtkdfAYi01obpX0DF7U6b1VRr30QNQ5xSFPITMdLSfhcBqjamE3F+sKcxPbD7eZuMHu3Qkk2V+JGxBDQ==", - "requires": { - "@octokit/types": "^2.0.1", - "deprecation": "^2.3.1" - }, - "dependencies": { - "@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", - "requires": { - "@types/node": ">= 8" - } - } - } - }, - "@octokit/request": { - "version": "5.6.3", - "resolved": "https://registry.npmjs.org/@octokit/request/-/request-5.6.3.tgz", - "integrity": "sha512-bFJl0I1KVc9jYTe9tdGGpAMPy32dLBXXo1dS/YwSCTL/2nd9XeHsY616RE3HPXDVk+a+dBuzyz5YdlXwcDTr2A==", - "requires": { - "@octokit/endpoint": "^6.0.1", - "@octokit/request-error": "^2.1.0", - "@octokit/types": "^6.16.1", - "is-plain-object": "^5.0.0", - "node-fetch": "^2.6.7", - "universal-user-agent": "^6.0.0" - } - }, - "@octokit/request-error": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-2.1.0.tgz", - "integrity": "sha512-1VIvgXxs9WHSjicsRwq8PlR2LR2x6DwsJAaFgzdi0JfJoGSO8mYI/cHJQ+9FbN21aa+DrgNLnwObmyeSC8Rmpg==", - "requires": { - "@octokit/types": "^6.0.3", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "@octokit/rest": { - "version": "16.43.2", - "resolved": "https://registry.npmjs.org/@octokit/rest/-/rest-16.43.2.tgz", - "integrity": "sha512-ngDBevLbBTFfrHZeiS7SAMAZ6ssuVmXuya+F/7RaVvlysgGa1JKJkKWY+jV6TCJYcW0OALfJ7nTIGXcBXzycfQ==", - "requires": { - "@octokit/auth-token": "^2.4.0", - "@octokit/plugin-paginate-rest": "^1.1.1", - "@octokit/plugin-request-log": "^1.0.0", - "@octokit/plugin-rest-endpoint-methods": "2.4.0", - "@octokit/request": "^5.2.0", - "@octokit/request-error": "^1.0.2", - "atob-lite": "^2.0.0", - "before-after-hook": "^2.0.0", - "btoa-lite": "^1.0.0", - "deprecation": "^2.0.0", - "lodash.get": "^4.4.2", - "lodash.set": "^4.3.2", - "lodash.uniq": "^4.5.0", - "octokit-pagination-methods": "^1.1.0", - "once": "^1.4.0", - "universal-user-agent": "^4.0.0" - }, - "dependencies": { - "@octokit/request-error": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@octokit/request-error/-/request-error-1.2.1.tgz", - "integrity": "sha512-+6yDyk1EES6WK+l3viRDElw96MvwfJxCt45GvmjDUKWjYIb3PJZQkq3i46TwGwoPD4h8NmTrENmtyA1FwbmhRA==", - "requires": { - "@octokit/types": "^2.0.0", - "deprecation": "^2.0.0", - "once": "^1.4.0" - } - }, - "@octokit/types": { - "version": "2.16.2", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-2.16.2.tgz", - "integrity": "sha512-O75k56TYvJ8WpAakWwYRN8Bgu60KrmX0z1KqFp1kNiFNkgW+JW+9EBKZ+S33PU6SLvbihqd+3drvPxKK68Ee8Q==", - "requires": { - "@types/node": ">= 8" - } - }, - "universal-user-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-4.0.1.tgz", - "integrity": "sha512-LnST3ebHwVL2aNe4mejI9IQh2HfZ1RLo8Io2HugSif8ekzD1TlWpHpColOB/eh8JHMLkGH3Akqf040I+4ylNxg==", - "requires": { - "os-name": "^3.1.0" - } - } - } - }, - "@octokit/types": { - "version": "6.34.0", - "resolved": "https://registry.npmjs.org/@octokit/types/-/types-6.34.0.tgz", - "integrity": "sha512-s1zLBjWhdEI2zwaoSgyOFoKSl109CUcVBCc7biPJ3aAf6LGLU6szDvi31JPU7bxfla2lqfhjbbg/5DdFNxOwHw==", - "requires": { - "@octokit/openapi-types": "^11.2.0" - } - }, - "@sentry/browser": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/browser/-/browser-5.30.0.tgz", - "integrity": "sha512-rOb58ZNVJWh1VuMuBG1mL9r54nZqKeaIlwSlvzJfc89vyfd7n6tQ1UXMN383QBz/MS5H5z44Hy5eE+7pCrYAfw==", - "requires": { - "@sentry/core": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/cli": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@sentry/cli/-/cli-2.2.0.tgz", - "integrity": "sha512-ywFtB8VHyWN248LuM67fsRtdMLif/SOHYY3zyef5WybvnAmRLDmGTWK//hSUCebsHBpehRIkmt4iMiyUXwgd5w==", - "dev": true, - "requires": { - "https-proxy-agent": "^5.0.0", - "node-fetch": "^2.6.7", - "npmlog": "^6.0.1", - "progress": "^2.0.3", - "proxy-from-env": "^1.1.0", - "which": "^2.0.2" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "are-we-there-yet": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.0.tgz", - "integrity": "sha512-0GWpv50YSOcLXaN6/FAKY3vfRbllXWV2xvfA/oKJF8pzFhWXPV+yjhJXDBbjscDYowv7Yw1A3uigpzn5iEGTyw==", - "dev": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^3.6.0" - } - }, - "gauge": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", - "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", - "dev": true, - "requires": { - "aproba": "^1.0.3 || ^2.0.0", - "color-support": "^1.1.3", - "console-control-strings": "^1.1.0", - "has-unicode": "^2.0.1", - "signal-exit": "^3.0.7", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wide-align": "^1.1.5" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "npmlog": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", - "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", - "dev": true, - "requires": { - "are-we-there-yet": "^3.0.0", - "console-control-strings": "^1.1.0", - "gauge": "^4.0.3", - "set-blocking": "^2.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "dev": true, - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "wide-align": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", - "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", - "dev": true, - "requires": { - "string-width": "^1.0.2 || 2 || 3 || 4" - } - } - } - }, - "@sentry/core": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-5.30.0.tgz", - "integrity": "sha512-TmfrII8w1PQZSZgPpUESqjB+jC6MvZJZdLtE/0hZ+SrnKhW3x5WlYLvTXZpcWePYBku7rl2wn1RZu6uT0qCTeg==", - "requires": { - "@sentry/hub": "5.30.0", - "@sentry/minimal": "5.30.0", - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/hub": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-5.30.0.tgz", - "integrity": "sha512-2tYrGnzb1gKz2EkMDQcfLrDTvmGcQPuWxLnJKXJvYTQDGLlEvi2tWz1VIHjunmOvJrB5aIQLhm+dcMRwFZDCqQ==", - "requires": { - "@sentry/types": "5.30.0", - "@sentry/utils": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/minimal": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-5.30.0.tgz", - "integrity": "sha512-BwWb/owZKtkDX+Sc4zCSTNcvZUq7YcH3uAVlmh/gtR9rmUvbzAA3ewLuB3myi4wWRAMEtny6+J/FN/x+2wn9Xw==", - "requires": { - "@sentry/hub": "5.30.0", - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sentry/node": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/node/-/node-6.19.6.tgz", - "integrity": "sha512-kHQMfsy40ZxxdS9zMPmXCOOLWOJbQj6/aVSHt/L1QthYcgkAi7NJQNXnQIPWQDe8eP3DfNIWM7dc446coqjXrQ==", - "requires": { - "@sentry/core": "6.19.6", - "@sentry/hub": "6.19.6", - "@sentry/types": "6.19.6", - "@sentry/utils": "6.19.6", - "cookie": "^0.4.1", - "https-proxy-agent": "^5.0.0", - "lru_map": "^0.3.3", - "tslib": "^1.9.3" - }, - "dependencies": { - "@sentry/core": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/core/-/core-6.19.6.tgz", - "integrity": "sha512-biEotGRr44/vBCOegkTfC9rwqaqRKIpFljKGyYU6/NtzMRooktqOhjmjmItNCMRknArdeaQwA8lk2jcZDXX3Og==", - "requires": { - "@sentry/hub": "6.19.6", - "@sentry/minimal": "6.19.6", - "@sentry/types": "6.19.6", - "@sentry/utils": "6.19.6", - "tslib": "^1.9.3" - } - }, - "@sentry/hub": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/hub/-/hub-6.19.6.tgz", - "integrity": "sha512-PuEOBZxvx3bjxcXmWWZfWXG+orojQiWzv9LQXjIgroVMKM/GG4QtZbnWl1hOckUj7WtKNl4hEGO2g/6PyCV/vA==", - "requires": { - "@sentry/types": "6.19.6", - "@sentry/utils": "6.19.6", - "tslib": "^1.9.3" - } - }, - "@sentry/minimal": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/minimal/-/minimal-6.19.6.tgz", - "integrity": "sha512-T1NKcv+HTlmd8EbzUgnGPl4ySQGHWMCyZ8a8kXVMZOPDzphN3fVIzkYzWmSftCWp0rpabXPt9aRF2mfBKU+mAQ==", - "requires": { - "@sentry/hub": "6.19.6", - "@sentry/types": "6.19.6", - "tslib": "^1.9.3" - } - }, - "@sentry/types": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-6.19.6.tgz", - "integrity": "sha512-QH34LMJidEUPZK78l+Frt3AaVFJhEmIi05Zf8WHd9/iTt+OqvCHBgq49DDr1FWFqyYWm/QgW/3bIoikFpfsXyQ==" - }, - "@sentry/utils": { - "version": "6.19.6", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-6.19.6.tgz", - "integrity": "sha512-fAMWcsguL0632eWrROp/vhPgI7sBj/JROWVPzpabwVkm9z3m1rQm6iLFn4qfkZL8Ozy6NVZPXOQ7EXmeU24byg==", - "requires": { - "@sentry/types": "6.19.6", - "tslib": "^1.9.3" - } - } - } - }, - "@sentry/types": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/types/-/types-5.30.0.tgz", - "integrity": "sha512-R8xOqlSTZ+htqrfteCWU5Nk0CDN5ApUTvrlvBuiH1DyP6czDZ4ktbZB0hAgBlVcK0U+qpD3ag3Tqqpa5Q67rPw==" - }, - "@sentry/utils": { - "version": "5.30.0", - "resolved": "https://registry.npmjs.org/@sentry/utils/-/utils-5.30.0.tgz", - "integrity": "sha512-zaYmoH0NWWtvnJjC9/CBseXMtKHm/tm40sz3YfJRxeQjyzRqNQPgivpd9R/oDJCYj999mzdW382p/qi2ypjLww==", - "requires": { - "@sentry/types": "5.30.0", - "tslib": "^1.9.3" - } - }, - "@sindresorhus/is": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.6.0.tgz", - "integrity": "sha512-t09vSN3MdfsyCHoFcTRCH/iUtG7OJ0CsjzB8cjAmKc/va/kIgeDI/TxsigdncE/4be734m0cvIYwNaV4i2XqAw==" - }, - "@sinonjs/commons": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.3.tgz", - "integrity": "sha512-xkNcLAn/wZaX14RPlwizcKicDk9G3F8m2nU3L7Ukm5zBgTwiT0wsoFAHx9Jq56fJA1z/7uKGtCRu16sOUCLIHQ==", - "dev": true, - "requires": { - "type-detect": "4.0.8" - } - }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" - } - }, - "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" - } - }, - "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", - "dev": true - }, - "@socket.io/component-emitter": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz", - "integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg==", - "dev": true - }, - "@szmarczak/http-timer": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", - "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", - "requires": { - "defer-to-connect": "^2.0.0" - } - }, - "@tsconfig/node10": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz", - "integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==", - "dev": true - }, - "@tsconfig/node12": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz", - "integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==", - "dev": true - }, - "@tsconfig/node14": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz", - "integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==", - "dev": true - }, - "@tsconfig/node16": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz", - "integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==", - "dev": true - }, - "@types/body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-ALYone6pm6QmwZoAgeyNksccT9Q4AWZQ6PvfwR37GT6r6FWUPguq6sUmNGSMV2Wr761oQoBxwGGa6DR5o1DC9g==", - "dev": true, - "requires": { - "@types/connect": "*", - "@types/node": "*" - } - }, - "@types/cacheable-request": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", - "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", - "requires": { - "@types/http-cache-semantics": "*", - "@types/keyv": "*", - "@types/node": "*", - "@types/responselike": "*" - } - }, - "@types/chai": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.3.0.tgz", - "integrity": "sha512-/ceqdqeRraGolFTcfoXNiqjyQhZzbINDngeoAq9GoHa8PPK1yNzTaxWjA6BFWp5Ua9JpXEMSS4s5i9tS0hOJtw==", - "dev": true - }, - "@types/connect": { - "version": "3.4.35", - "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.35.tgz", - "integrity": "sha512-cdeYyv4KWoEgpBISTxWvqYsVy444DOqehiF3fM3ne10AmJ62RSyNkUnxMJXHQWRQQX2eR94m5y1IZyDwBjV9FQ==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/cookie": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.4.1.tgz", - "integrity": "sha512-XW/Aa8APYr6jSVVA1y/DEIZX0/GMKLEVekNG727R8cs56ahETkRAy/3DR7+fJyh7oUgGwNQaRfXCun0+KbWY7Q==", - "dev": true - }, - "@types/cors": { - "version": "2.8.13", - "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.13.tgz", - "integrity": "sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/express": { - "version": "4.17.13", - "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.13.tgz", - "integrity": "sha512-6bSZTPaTIACxn48l50SR+axgrqm6qXFIxrdAKaG6PaJk3+zuUr35hBlgT7vOmJcum+OEaIBLtHV/qloEAFITeA==", - "dev": true, - "requires": { - "@types/body-parser": "*", - "@types/express-serve-static-core": "^4.17.18", - "@types/qs": "*", - "@types/serve-static": "*" - } - }, - "@types/express-serve-static-core": { - "version": "4.17.28", - "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.17.28.tgz", - "integrity": "sha512-P1BJAEAW3E2DJUlkgq4tOL3RyMunoWXqbSCygWo5ZIWTjUgN1YnaXWW4VWl/oc8vs/XoYibEGBKP0uZyF4AHig==", - "dev": true, - "requires": { - "@types/node": "*", - "@types/qs": "*", - "@types/range-parser": "*" - } - }, - "@types/http-cache-semantics": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", - "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" - }, - "@types/json-buffer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@types/json-buffer/-/json-buffer-3.0.0.tgz", - "integrity": "sha512-3YP80IxxFJB4b5tYC2SUPwkg0XQLiu0nWvhRgEatgjf+29IcWO9X1k8xRv5DGssJ/lCrjYTjQPcobJr2yWIVuQ==" - }, - "@types/keyv": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.4.tgz", - "integrity": "sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==", - "requires": { - "@types/node": "*" - } - }, - "@types/mime": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.2.tgz", - "integrity": "sha512-YATxVxgRqNH6nHEIsvg6k2Boc1JHI9ZbH5iWFFv/MTkchz3b1ieGDa5T0a9RznNdI0KhVbdbWSN+KWWrQZRxTw==", - "dev": true - }, - "@types/mocha": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-7.0.2.tgz", - "integrity": "sha512-ZvO2tAcjmMi8V/5Z3JsyofMe3hasRcaw88cto5etSVMwVQfeivGAlEYmaQgceUSVYFofVjT+ioHsATjdWcFt1w==", - "dev": true - }, - "@types/multer": { - "version": "1.4.7", - "resolved": "https://registry.npmjs.org/@types/multer/-/multer-1.4.7.tgz", - "integrity": "sha512-/SNsDidUFCvqqcWDwxv2feww/yqhNeTRL5CVoL3jU4Goc4kKEL10T7Eye65ZqPNi4HRx8sAEX59pV1aEH7drNA==", - "dev": true, - "requires": { - "@types/express": "*" - } - }, - "@types/node": { - "version": "11.15.54", - "resolved": "https://registry.npmjs.org/@types/node/-/node-11.15.54.tgz", - "integrity": "sha512-1RWYiq+5UfozGsU6MwJyFX6BtktcT10XRjvcAQmskCtMcW3tPske88lM/nHv7BQG1w9KBXI1zPGuu5PnNCX14g==" - }, - "@types/qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==", - "dev": true - }, - "@types/range-parser": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.4.tgz", - "integrity": "sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==", - "dev": true - }, - "@types/responselike": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", - "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", - "requires": { - "@types/node": "*" - } - }, - "@types/semver": { - "version": "7.3.9", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz", - "integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==", - "dev": true - }, - "@types/serve-static": { - "version": "1.13.10", - "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.13.10.tgz", - "integrity": "sha512-nCkHGI4w7ZgAdNkrEu0bv+4xNV/XDqW+DydknebMOQwkpDGx8G+HTlj7R7ABI8i8nKxVw0wtKPi1D+lPOkh4YQ==", - "dev": true, - "requires": { - "@types/mime": "^1", - "@types/node": "*" - } - }, - "@types/ws": { - "version": "8.5.3", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", - "integrity": "sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/xml2js": { - "version": "0.4.9", - "resolved": "https://registry.npmjs.org/@types/xml2js/-/xml2js-0.4.9.tgz", - "integrity": "sha512-CHiCKIihl1pychwR2RNX5mAYmJDACgFVCMT5OArMaO3erzwXVcBqPcusr+Vl8yeeXukxZqtF8mZioqX+mpjjdw==", - "dev": true, - "requires": { - "@types/node": "*" - } - }, - "@types/yauzl": { - "version": "2.9.2", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", - "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", - "optional": true, - "requires": { - "@types/node": "*" - } - }, - "abbrev": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", - "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "optional": true - }, - "accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", - "requires": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" - } - }, - "acorn": { - "version": "8.7.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.7.0.tgz", - "integrity": "sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==", - "dev": true - }, - "acorn-walk": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", - "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", - "dev": true - }, - "agent-base": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", - "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", - "requires": { - "debug": "4" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "optional": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-colors": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", - "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "optional": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "anymatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", - "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", - "dev": true, - "requires": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - } - }, - "append-field": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", - "integrity": "sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY=" - }, - "aproba": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", - "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", - "devOptional": true - }, - "are-we-there-yet": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.7.tgz", - "integrity": "sha512-nxwy40TuMiUGqMyRHgCSWZ9FM4VAoRP4xUYSTv5ImRog+h9yISPbVH7H8fASCIzYn9wlEv4zvFL7uKDMCFQm3g==", - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "arg": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", - "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", - "dev": true - }, - "argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" - }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, - "asn1": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", - "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", - "optional": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true - }, - "assertion-error": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", - "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", - "dev": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "optional": true - }, - "atob-lite": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/atob-lite/-/atob-lite-2.0.0.tgz", - "integrity": "sha512-LEeSAWeh2Gfa2FtlQE1shxQ8zi5F9GHarrGKz08TMdODD5T4eH6BMsvtnhbWZ+XQn+Gb6om/917ucvRu7l7ukw==" - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "optional": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "optional": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "devOptional": true - }, - "base64id": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", - "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "before-after-hook": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-2.2.2.tgz", - "integrity": "sha512-3pZEU3NT5BFUo/AD5ERPWOgQOCZITni6iavr5AUw5AUwQjMlI0kzu5btnyD39AF0gUEsDPwJT+oY1ORBJijPjQ==" - }, - "binary-extensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", - "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", - "dev": true - }, - "bindings": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", - "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", - "optional": true, - "requires": { - "file-uri-to-path": "1.0.0" - } - }, - "body-parser": { - "version": "1.19.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.19.2.tgz", - "integrity": "sha512-SAAwOxgoCKMGs9uUAUFHygfLAyaniaoun6I8mFY9pRAJL9+Kec34aU+oIjDhTycub1jozEfEwx1W1IuOYxVSFw==", - "requires": { - "bytes": "3.1.2", - "content-type": "~1.0.4", - "debug": "2.6.9", - "depd": "~1.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "on-finished": "~2.3.0", - "qs": "6.9.7", - "raw-body": "2.4.3", - "type-is": "~1.6.18" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "devOptional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", - "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", - "dev": true, - "requires": { - "fill-range": "^7.0.1" - } - }, - "browser-stdout": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", - "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==", - "dev": true - }, - "browserstack": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/browserstack/-/browserstack-1.5.3.tgz", - "integrity": "sha512-AO+mECXsW4QcqC9bxwM29O7qWa7bJT94uBFzeb5brylIQwawuEziwq20dPYbins95GlWzOawgyDNdjYAo32EKg==", - "dev": true, - "requires": { - "https-proxy-agent": "^2.2.1" - }, - "dependencies": { - "agent-base": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz", - "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==", - "dev": true, - "requires": { - "es6-promisify": "^5.0.0" - } - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - }, - "https-proxy-agent": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz", - "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==", - "dev": true, - "requires": { - "agent-base": "^4.3.0", - "debug": "^3.1.0" - } - } - } - }, - "browserstack-local": { - "version": "1.4.9", - "resolved": "https://registry.npmjs.org/browserstack-local/-/browserstack-local-1.4.9.tgz", - "integrity": "sha512-V+q8HQwRQFr9nd32xR66ZZ3VDWa3Kct4IMMudhKgcuD7cWrvvFARZOibx71II+Rf7P5nMQpWWxl9z/3p927nbg==", - "dev": true, - "requires": { - "https-proxy-agent": "^4.0.0", - "is-running": "^2.1.0", - "ps-tree": "=1.2.0", - "temp-fs": "^0.9.9" - }, - "dependencies": { - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==", - "dev": true - }, - "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "dev": true, - "requires": { - "agent-base": "5", - "debug": "4" - } - } - } - }, - "btoa-lite": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/btoa-lite/-/btoa-lite-1.0.0.tgz", - "integrity": "sha512-gvW7InbIyF8AicrqWoptdW08pUxuhq8BEgowNajy9RhiE86fmGAGl+bLKo6oB8QP0CkqHLowfN0oJdKC/J6LbA==" - }, - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" - }, - "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" - }, - "bytes": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", - "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==" - }, - "cacheable-lookup": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", - "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" - }, - "cacheable-request": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", - "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", - "requires": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - } - }, - "camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "optional": true - }, - "chai": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.4.tgz", - "integrity": "sha512-yS5H68VYOCtN1cjfwumDSuzn/9c+yza4f3reKXlE5rUg7SFcCEy90gJvydNgOYtblyf4Zi6jIWRnXOgErta0KA==", - "dev": true, - "requires": { - "assertion-error": "^1.1.0", - "check-error": "^1.0.2", - "deep-eql": "^3.0.1", - "get-func-name": "^2.0.0", - "pathval": "^1.1.1", - "type-detect": "^4.0.5" - } - }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "check-error": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=", - "dev": true - }, - "chokidar": { - "version": "3.5.1", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.1.tgz", - "integrity": "sha512-9+s+Od+W0VJJzawDma/gvBNQqkTiqYTWLuZoyAsivsI4AaWTCzHG06/TMjsf1cYe9Cb97UCEhjz7HvnPk2p/tw==", - "dev": true, - "requires": { - "anymatch": "~3.1.1", - "braces": "~3.0.2", - "fsevents": "~2.3.1", - "glob-parent": "~5.1.0", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.5.0" - } - }, - "chownr": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", - "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", - "optional": true - }, - "cliui": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", - "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^7.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "clone-response": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "optional": true - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "color-support": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", - "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", - "dev": true - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "optional": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commander": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-3.0.2.tgz", - "integrity": "sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow==" - }, - "compress-brotli": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/compress-brotli/-/compress-brotli-1.3.6.tgz", - "integrity": "sha512-au99/GqZtUtiCBliqLFbWlhnCxn+XSYjwZ77q6mKN4La4qOXDoLVPZ50iXr0WmAyMxl8yqoq3Yq4OeQNPPkyeQ==", - "requires": { - "@types/json-buffer": "~3.0.0", - "json-buffer": "~3.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "devOptional": true - }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "connect": { - "version": "3.7.0", - "resolved": "https://registry.npmjs.org/connect/-/connect-3.7.0.tgz", - "integrity": "sha512-ZqRXc+tZukToSNmh5C2iWMSoV3X1YUcPbqEM4DkEG5tNQXrQUZCNVGGv3IuicnkMtPfGf3Xtp8WCXs295iQ1pQ==", - "dev": true, - "requires": { - "debug": "2.6.9", - "finalhandler": "1.1.2", - "parseurl": "~1.3.3", - "utils-merge": "1.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - } - } - }, - "console-control-strings": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "devOptional": true - }, - "content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", - "requires": { - "safe-buffer": "5.2.1" - } - }, - "content-type": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", - "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" - }, - "convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" - }, - "cookie": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", - "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" - }, - "cookie-signature": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" - }, - "copyfiles": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/copyfiles/-/copyfiles-2.4.1.tgz", - "integrity": "sha512-fereAvAvxDrQDOXybk3Qu3dPbOoKoysFMWtkY3mv5BsL8//OSZVL5DCLYqgRfY5cWirgRzlC+WSrxp6Bo3eNZg==", - "dev": true, - "requires": { - "glob": "^7.0.5", - "minimatch": "^3.0.3", - "mkdirp": "^1.0.4", - "noms": "0.0.0", - "through2": "^2.0.1", - "untildify": "^4.0.0", - "yargs": "^16.1.0" - }, - "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "dev": true - } - } - }, - "core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" - }, - "cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", - "dev": true, - "requires": { - "object-assign": "^4", - "vary": "^1" - } - }, - "create-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", - "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", - "dev": true - }, - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "dev": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "custom-event": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=", - "dev": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "date-format": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/date-format/-/date-format-4.0.6.tgz", - "integrity": "sha512-B9vvg5rHuQ8cbUXE/RMWMyX2YA5TecT3jKF5fLtGNlzPlU7zblSPmAm2OImDbWL+LDOQ6pUm+4LOFz+ywS41Zw==", - "dev": true - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" - }, - "decompress-response": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", - "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", - "requires": { - "mimic-response": "^3.1.0" - }, - "dependencies": { - "mimic-response": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", - "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" - } - } - }, - "deep-eql": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz", - "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==", - "dev": true, - "requires": { - "type-detect": "^4.0.0" - } - }, - "defer-to-connect": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", - "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" - }, - "define-lazy-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz", - "integrity": "sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og==" - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "optional": true - }, - "delegates": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", - "devOptional": true - }, - "depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" - }, - "deprecation": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/deprecation/-/deprecation-2.3.1.tgz", - "integrity": "sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==" - }, - "destroy": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", - "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" - }, - "di": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/di/-/di-0.0.1.tgz", - "integrity": "sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw=", - "dev": true - }, - "diff": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz", - "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==", - "dev": true - }, - "doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "requires": { - "esutils": "^2.0.2" - } - }, - "dom-serialize": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/dom-serialize/-/dom-serialize-2.2.1.tgz", - "integrity": "sha1-ViromZ9Evl6jB29UGdzVnrQ6yVs=", - "dev": true, - "requires": { - "custom-event": "~1.0.0", - "ent": "~2.2.0", - "extend": "^3.0.0", - "void-elements": "^2.0.0" - } - }, - "duplexer": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", - "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", - "dev": true - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "edge-launcher": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/edge-launcher/-/edge-launcher-1.2.2.tgz", - "integrity": "sha1-60Cq+9Bnpup27/+rBke81VCbN7I=", - "dev": true - }, - "ee-first": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "encodeurl": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "requires": { - "once": "^1.4.0" - } - }, - "engine.io": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.4.0.tgz", - "integrity": "sha512-OgxY1c/RuCSeO/rTr8DIFXx76IzUUft86R7/P7MMbbkuzeqJoTNw2lmeD91IyGz41QYleIIjWeMJGgug043sfQ==", - "dev": true, - "requires": { - "@types/cookie": "^0.4.1", - "@types/cors": "^2.8.12", - "@types/node": ">=10.0.0", - "accepts": "~1.3.4", - "base64id": "2.0.0", - "cookie": "~0.4.1", - "cors": "~2.8.5", - "debug": "~4.3.1", - "engine.io-parser": "~5.0.3", - "ws": "~8.11.0" - } - }, - "engine.io-parser": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz", - "integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==", - "dev": true - }, - "ent": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/ent/-/ent-2.2.0.tgz", - "integrity": "sha1-6WQhkyWiHQX0RGai9obtbOX13R0=", - "dev": true - }, - "entities": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-2.1.0.tgz", - "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==", - "dev": true - }, - "env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "optional": true - }, - "es6-promise": { - "version": "4.2.8", - "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", - "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w==", - "dev": true - }, - "es6-promisify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz", - "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=", - "dev": true, - "requires": { - "es6-promise": "^4.0.3" - } - }, - "es6-shim": { - "version": "0.35.6", - "resolved": "https://registry.npmjs.org/es6-shim/-/es6-shim-0.35.6.tgz", - "integrity": "sha512-EmTr31wppcaIAgblChZiuN/l9Y7DPyw8Xtbg7fIVngn6zMW+IEBJDJngeKC3x6wr0V/vcA2wqeFnaw1bFJbDdA==" - }, - "escalade": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", - "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", - "dev": true - }, - "escape-html": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "esutils": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", - "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", - "dev": true - }, - "etag": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" - }, - "event-stream": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/event-stream/-/event-stream-3.3.4.tgz", - "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", - "dev": true, - "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", - "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" - } - }, - "eventemitter3": { - "version": "4.0.7", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", - "integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==" - }, - "execa": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz", - "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==", - "requires": { - "cross-spawn": "^6.0.0", - "get-stream": "^4.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - } - }, - "get-stream": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz", - "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==", - "requires": { - "pump": "^3.0.0" - } - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" - }, - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "express": { - "version": "4.17.3", - "resolved": "https://registry.npmjs.org/express/-/express-4.17.3.tgz", - "integrity": "sha512-yuSQpz5I+Ch7gFrPCk4/c+dIBKlQUxtgwqzph132bsT6qhuzss6I8cLJQz7B3rFblzd6wtcI0ZbGltH/C4LjUg==", - "requires": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.19.2", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.4.2", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "depd": "~1.1.2", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.1.2", - "fresh": "0.5.2", - "merge-descriptors": "1.0.1", - "methods": "~1.1.2", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.7", - "qs": "6.9.7", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.17.2", - "serve-static": "1.14.2", - "setprototypeof": "1.2.0", - "statuses": "~1.5.0", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "devOptional": true - }, - "extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "requires": { - "@types/yauzl": "^2.9.1", - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "optional": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "optional": true - }, - "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", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "optional": true - }, - "fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", - "requires": { - "pend": "~1.2.0" - } - }, - "file": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/file/-/file-0.2.2.tgz", - "integrity": "sha1-w9/Y+M81Na5FXCtCPC5SY112tNM=", - "dev": true - }, - "file-uri-to-path": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", - "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", - "optional": true - }, - "fill-range": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", - "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", - "dev": true, - "requires": { - "to-regex-range": "^5.0.1" - } - }, - "finalhandler": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.2.tgz", - "integrity": "sha512-aAWcW57uxVNrQZqFXjITpW3sIUQmHGG3qSb9mUah9MgMC4NeWhNOlNjXEYq3HjRAvL6arUviZGGJsBg6z0zsWA==", - "requires": { - "debug": "2.6.9", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "on-finished": "~2.3.0", - "parseurl": "~1.3.3", - "statuses": "~1.5.0", - "unpipe": "~1.0.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "encoding": { + "optional": true } } }, - "find-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", - "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", - "dev": true, - "requires": { - "locate-path": "^6.0.0", - "path-exists": "^4.0.0" + "node_modules/node-gyp": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-9.3.1.tgz", + "integrity": "sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^10.0.3", + "nopt": "^6.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": "^12.13 || ^14.13 || >=16" } }, - "flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "dev": true - }, - "flatted": { - "version": "3.2.5", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", - "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", - "dev": true - }, - "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "optional": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "node_modules/node-windows-trayicon": { + "name": "windows-trayicon", + "version": "3.0.0", + "resolved": "git+ssh://git@github.com/keymanapp/node-windows-trayicon.git#b3dda72327141cd99b4e21d3f5d7002bb17c2131", + "hasInstallScript": true, + "license": "MIT", "optional": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" + "os": [ + "win32" + ], + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^3.1.0", + "node-gyp": "^9.1.0" } }, - "forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==" - }, - "fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" - }, - "from": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/from/-/from-0.1.7.tgz", - "integrity": "sha1-g8YK/Fi5xWmXAH7Rp2izqzA6RP4=", - "dev": true - }, - "fs-access": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/fs-access/-/fs-access-1.0.1.tgz", - "integrity": "sha1-1qh/JiJxzv6+wwxVNAf7mV2od3o=", + "node_modules/noms": { + "version": "0.0.0", "dev": true, - "requires": { - "null-check": "^1.0.0" + "license": "ISC", + "dependencies": { + "inherits": "^2.0.1", + "readable-stream": "~1.0.31" } }, - "fs-extra": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.0.1.tgz", - "integrity": "sha512-NbdoVMZso2Lsrn/QwLXOy6rm0ufY2zEOKCDzJR/0kBsb0E6qed0P3iYK+Ath3BfvXEeu4JhEtXLgILx5psUfag==", + "node_modules/noms/node_modules/isarray": { + "version": "0.0.1", "dev": true, - "requires": { - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - } + "license": "MIT" }, - "fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "optional": true, - "requires": { - "minipass": "^3.0.0" + "node_modules/noms/node_modules/readable-stream": { + "version": "1.0.34", + "dev": true, + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.1", + "isarray": "0.0.1", + "string_decoder": "~0.10.x" } }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "devOptional": true - }, - "fsevents": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", - "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "node_modules/noms/node_modules/string_decoder": { + "version": "0.10.31", "dev": true, - "optional": true + "license": "MIT" }, - "gauge": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", - "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "node_modules/nopt": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-6.0.0.tgz", + "integrity": "sha512-ZwLpbTgdhuZUnZzjd7nb1ZV+4DoiC6/sfiVKok72ym/4Tlf+DFdlHYmT2JPmcNNWV6Pi3SDf1kT+A4r9RTuT9g==", "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" + "dependencies": { + "abbrev": "^1.0.0" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + "node_modules/normalize-path": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "get-func-name": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=", - "dev": true + "node_modules/normalize-url": { + "version": "6.1.0", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "requires": { - "pump": "^3.0.0" + "node_modules/npm-run-path": { + "version": "2.0.2", + "license": "MIT", + "dependencies": { + "path-key": "^2.0.0" + }, + "engines": { + "node": ">=4" } }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "optional": true, - "requires": { - "assert-plus": "^1.0.0" + "node_modules/npm-run-path/node_modules/path-key": { + "version": "2.0.1", + "license": "MIT", + "engines": { + "node": ">=4" } }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "node_modules/npmlog": { + "version": "6.0.2", "devOptional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "node_modules/null-check": { + "version": "1.0.0", "dev": true, - "requires": { - "is-glob": "^4.0.1" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "google-closure-compiler-java": { - "version": "20200224.0.0", - "resolved": "https://registry.npmjs.org/google-closure-compiler-java/-/google-closure-compiler-java-20200224.0.0.tgz", - "integrity": "sha512-palFcDoScauZjWIsGDzMK6h+IctcRb55I3wJX8Ko/DTSz72xwadRdKm0lGt8OoYL7SKEO+IjgD7s8XrAGpLnlQ==", - "dev": true - }, - "got": { - "version": "11.8.5", - "resolved": "https://registry.npmjs.org/got/-/got-11.8.5.tgz", - "integrity": "sha512-o0Je4NvQObAuZPHLFoRSkdG2lTgtcynqymzg2Vupdx6PorhaT5MCbIyXG6d4D94kk8ZG57QeosgdiqfJWhEhlQ==", - "requires": { - "@sindresorhus/is": "^4.0.0", - "@szmarczak/http-timer": "^4.0.5", - "@types/cacheable-request": "^6.0.1", - "@types/responselike": "^1.0.0", - "cacheable-lookup": "^5.0.3", - "cacheable-request": "^7.0.2", - "decompress-response": "^6.0.0", - "http2-wrapper": "^1.0.0-beta.5.2", - "lowercase-keys": "^2.0.0", - "p-cancelable": "^2.0.0", - "responselike": "^2.0.0" + "node_modules/number-is-nan": { + "version": "1.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "graceful-fs": { - "version": "4.2.10", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", - "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", - "devOptional": true + "node_modules/object-assign": { + "version": "4.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "optional": true + "node_modules/octokit-pagination-methods": { + "version": "1.1.0", + "license": "MIT" }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "optional": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" + "node_modules/on-finished": { + "version": "2.3.0", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-unicode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", - "devOptional": true + "node_modules/once": { + "version": "1.4.0", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } }, - "he": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", - "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", - "dev": true + "node_modules/open": { + "version": "8.4.0", + "license": "MIT", + "dependencies": { + "define-lazy-prop": "^2.0.0", + "is-docker": "^2.1.1", + "is-wsl": "^2.2.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "hetrodo-node-hide-console-window-napi": { - "version": "git+ssh://git@github.com/keymanapp/hetrodo-node-hide-console-window-napi.git#858b23036a9963b40ad6ff3c5bacd421e5839b92", - "from": "hetrodo-node-hide-console-window-napi@git+ssh://git@github.com/keymanapp/hetrodo-node-hide-console-window-napi.git#858b23036a9963b40ad6ff3c5bacd421e5839b92", - "optional": true + "node_modules/os-name": { + "version": "3.1.0", + "license": "MIT", + "dependencies": { + "macos-release": "^2.2.0", + "windows-release": "^3.1.0" + }, + "engines": { + "node": ">=6" + } }, - "hpagent": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz", - "integrity": "sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==", - "optional": true + "node_modules/p-cancelable": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "http-cache-semantics": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "node_modules/p-finally": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "requires": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" + "node_modules/p-limit": { + "version": "3.1.0", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "http-proxy": { - "version": "1.18.1", - "resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz", - "integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==", + "node_modules/p-locate": { + "version": "5.0.0", "dev": true, - "requires": { - "eventemitter3": "^4.0.0", - "follow-redirects": "^1.0.0", - "requires-port": "^1.0.0" + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "optional": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "http2-wrapper": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", - "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", - "requires": { - "quick-lru": "^5.1.1", - "resolve-alpn": "^1.0.0" + "node_modules/p-try": { + "version": "2.2.0", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "https-proxy-agent": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz", - "integrity": "sha512-EkYm5BcKUGiduxzSt3Eppko+PiNWNEpa4ySk9vTC6wDsQJW9rHSa+UhGNJoRYp7bz6Ht1eaRIa6QaJqO5rCFbA==", - "requires": { - "agent-base": "6", - "debug": "4" - } + "node_modules/pako": { + "version": "1.0.11", + "license": "(MIT AND Zlib)" }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "node_modules/parseurl": { + "version": "1.3.3", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "immediate": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=", - "dev": true + "node_modules/path-exists": { + "version": "4.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "node_modules/path-is-absolute": { + "version": "1.0.1", "devOptional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "ipaddr.js": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", - "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==" - }, - "is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "node_modules/path-key": { + "version": "3.1.1", "dev": true, - "requires": { - "binary-extensions": "^2.0.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "is-docker": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz", - "integrity": "sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==" + "node_modules/path-parse": { + "version": "1.0.7", + "dev": true, + "license": "MIT" }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true + "node_modules/path-to-regexp": { + "version": "0.1.7", + "license": "MIT" }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" + "node_modules/pathval": { + "version": "1.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" } }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "node_modules/pause-stream": { + "version": "0.0.11", "dev": true, - "requires": { - "is-extglob": "^2.1.1" + "license": [ + "MIT", + "Apache2" + ], + "dependencies": { + "through": "~2.3" } }, - "is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true + "node_modules/pend": { + "version": "1.2.0", + "license": "MIT" }, - "is-plain-object": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", - "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + "node_modules/picomatch": { + "version": "2.2.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } }, - "is-running": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-running/-/is-running-2.1.0.tgz", - "integrity": "sha1-MKc/9cw4VOT8JUkICen1q/jeCeA=", - "dev": true + "node_modules/process-nextick-args": { + "version": "2.0.1", + "license": "MIT" }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" + "node_modules/progress": { + "version": "2.0.3", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", "optional": true }, - "is-unicode-supported": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", - "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true - }, - "is-wsl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz", - "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==", - "requires": { - "is-docker": "^2.0.0" + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" } }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isbinaryfile": { - "version": "4.0.10", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.10.tgz", - "integrity": "sha512-iHrqe5shvBUcFbmZq9zOQHBoeOhZJu6RQGrDpBgenUm/Am+F3JM2MgQj+rK3Z601fzrL5gLZWtAPH2OBaSVcyw==", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + "node_modules/proxy-addr": { + "version": "2.0.7", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "optional": true + "node_modules/proxy-from-env": { + "version": "1.1.0", + "dev": true, + "license": "MIT" }, - "js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "node_modules/ps-tree": { + "version": "1.2.0", "dev": true, - "requires": { - "argparse": "^2.0.1" + "license": "MIT", + "dependencies": { + "event-stream": "=3.3.4" + }, + "bin": { + "ps-tree": "bin/ps-tree.js" + }, + "engines": { + "node": ">= 0.10" } }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-buffer": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" - }, - "json-schema": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", - "integrity": "sha512-es94M3nTIfsEPisRafak+HDLfHXnKBhV3vU5eqPcS3flIWqcxJWgXHXiey3YrpaNsanY5ei1VoYEbOzijuq9BA==", - "optional": true - }, - "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==", - "optional": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "optional": true + "node_modules/pump": { + "version": "3.0.0", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, - "jsonfile": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", - "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.6", - "universalify": "^2.0.0" + "node_modules/punycode": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=6" } }, - "jsprim": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", - "integrity": "sha512-P2bSOMAc/ciLz6DzgjVlGJP9+BrJWu5UDGK70C2iweC5QBIeFf0ZXRvGjEj2uYgrY2MkAAhsSWHDWlFtEroZWw==", - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.4.0", - "verror": "1.10.0" + "node_modules/q": { + "version": "1.5.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.0", + "teleport": ">=0.2.0" } }, - "jszip": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/jszip/-/jszip-3.9.1.tgz", - "integrity": "sha512-H9A60xPqJ1CuC4Ka6qxzXZeU8aNmgOeP5IFqwJbQQwtu2EUYxota3LdsiZWplF7Wgd9tkAd0mdu36nceSaPuYw==", + "node_modules/qjobs": { + "version": "1.2.0", "dev": true, - "requires": { - "lie": "~3.3.0", - "pako": "~1.0.2", - "readable-stream": "~2.3.6", - "set-immediate-shim": "~1.0.1" + "license": "MIT", + "engines": { + "node": ">=0.9" } }, - "just-extend": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", - "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", - "dev": true + "node_modules/qs": { + "version": "6.9.7", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "karma": { - "version": "6.4.1", - "resolved": "https://registry.npmjs.org/karma/-/karma-6.4.1.tgz", - "integrity": "sha512-Cj57NKOskK7wtFWSlMvZf459iX+kpYIPXmkNUzP2WAFcA7nhr/ALn5R7sw3w+1udFDcpMx/tuB8d5amgm3ijaA==", - "dev": true, - "requires": { - "@colors/colors": "1.5.0", - "body-parser": "^1.19.0", - "braces": "^3.0.2", - "chokidar": "^3.5.1", - "connect": "^3.7.0", - "di": "^0.0.1", - "dom-serialize": "^2.2.1", - "glob": "^7.1.7", - "graceful-fs": "^4.2.6", - "http-proxy": "^1.18.1", - "isbinaryfile": "^4.0.8", - "lodash": "^4.17.21", - "log4js": "^6.4.1", - "mime": "^2.5.2", - "minimatch": "^3.0.4", - "mkdirp": "^0.5.5", - "qjobs": "^1.2.0", - "range-parser": "^1.2.1", - "rimraf": "^3.0.2", - "socket.io": "^4.4.1", - "source-map": "^0.6.1", - "tmp": "^0.2.1", - "ua-parser-js": "^0.7.30", - "yargs": "^16.1.1" + "node_modules/quick-lru": { + "version": "5.1.1", + "license": "MIT", + "engines": { + "node": ">=10" }, - "dependencies": { - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "karma-browserstack-launcher": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/karma-browserstack-launcher/-/karma-browserstack-launcher-1.6.0.tgz", - "integrity": "sha512-Y/UWPdHZkHIVH2To4GWHCTzmrsB6H7PBWy6pw+TWz5sr4HW2mcE+Uj6qWgoVNxvQU1Pfn5LQQzI6EQ65p8QbiQ==", + "node_modules/randombytes": { + "version": "2.1.0", "dev": true, - "requires": { - "browserstack": "~1.5.1", - "browserstack-local": "^1.3.7", - "q": "~1.5.0" + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" } }, - "karma-chai": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/karma-chai/-/karma-chai-0.1.0.tgz", - "integrity": "sha1-vuWtQEAFF4Ea40u5RfdikJEIt5o=", - "dev": true, - "requires": {} - }, - "karma-chrome-launcher": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/karma-chrome-launcher/-/karma-chrome-launcher-2.2.0.tgz", - "integrity": "sha512-uf/ZVpAabDBPvdPdveyk1EPgbnloPvFFGgmRhYLTDH7gEB4nZdSBk8yTU47w1g/drLSx5uMOkjKk7IWKfWg/+w==", - "dev": true, - "requires": { - "fs-access": "^1.0.0", - "which": "^1.2.1" - }, - "dependencies": { - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "dev": true, - "requires": { - "isexe": "^2.0.0" - } - } + "node_modules/range-parser": { + "version": "1.2.1", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "karma-edge-launcher": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/karma-edge-launcher/-/karma-edge-launcher-0.4.2.tgz", - "integrity": "sha512-YAJZb1fmRcxNhMIWYsjLuxwODBjh2cSHgTW/jkVmdpGguJjLbs9ZgIK/tEJsMQcBLUkO+yO4LBbqYxqgGW2HIw==", - "dev": true, - "requires": { - "edge-launcher": "1.2.2" + "node_modules/raw-body": { + "version": "2.4.3", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "1.8.1", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "karma-firefox-launcher": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/karma-firefox-launcher/-/karma-firefox-launcher-1.3.0.tgz", - "integrity": "sha512-Fi7xPhwrRgr+94BnHX0F5dCl1miIW4RHnzjIGxF8GaIEp7rNqX7LSi7ok63VXs3PS/5MQaQMhGxw+bvD+pibBQ==", - "dev": true, - "requires": { - "is-wsl": "^2.1.0" + "node_modules/readable-stream": { + "version": "2.3.7", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" } }, - "karma-fixture": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/karma-fixture/-/karma-fixture-0.2.6.tgz", - "integrity": "sha1-lxzqjCFtc/BwQ5ZMtz8Q4IMAGO8=", - "dev": true + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" }, - "karma-html2js-preprocessor": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/karma-html2js-preprocessor/-/karma-html2js-preprocessor-1.1.0.tgz", - "integrity": "sha1-/Ant8Eu+K7bu6boZaPgmtziAIL0=", + "node_modules/readdirp": { + "version": "3.5.0", "dev": true, - "requires": {} + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } }, - "karma-ie-launcher": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-ie-launcher/-/karma-ie-launcher-1.0.0.tgz", - "integrity": "sha1-SXmGhCxJAZA0bNifVJTKmDDG1Zw=", + "node_modules/rechoir": { + "version": "0.6.2", "dev": true, - "requires": { - "lodash": "^4.6.1" + "dependencies": { + "resolve": "^1.1.6" + }, + "engines": { + "node": ">= 0.10" } }, - "karma-json-fixtures-preprocessor": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/karma-json-fixtures-preprocessor/-/karma-json-fixtures-preprocessor-0.0.6.tgz", - "integrity": "sha1-T3ii6800OH+OVaur/2NGVRZMTHY=", - "dev": true + "node_modules/require-directory": { + "version": "2.1.1", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "karma-mocha": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/karma-mocha/-/karma-mocha-2.0.1.tgz", - "integrity": "sha512-Tzd5HBjm8his2OA4bouAsATYEpZrp9vC7z5E5j4C5Of5Rrs1jY67RAwXNcVmd/Bnk1wgvQRou0zGVLey44G4tQ==", - "dev": true, - "requires": { - "minimist": "^1.2.3" + "node_modules/require-from-string": { + "version": "2.0.2", + "license": "MIT", + "engines": { + "node": ">=0.10.0" } }, - "karma-mocha-reporter": { - "version": "2.2.5", - "resolved": "https://registry.npmjs.org/karma-mocha-reporter/-/karma-mocha-reporter-2.2.5.tgz", - "integrity": "sha1-FRIAlejtgZGG5HoLAS8810GJVWA=", + "node_modules/require-main-filename": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/requirejs": { + "version": "2.3.6", "dev": true, - "requires": { - "chalk": "^2.1.0", - "log-symbols": "^2.1.0", - "strip-ansi": "^4.0.0" + "license": "MIT", + "bin": { + "r_js": "bin/r.js", + "r.js": "bin/r.js" }, - "dependencies": { - "ansi-regex": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", - "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", - "dev": true - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "dev": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "dev": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "dev": true, - "requires": { - "chalk": "^2.0.1" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "engines": { + "node": ">=0.4.0" } }, - "karma-safari-launcher": { + "node_modules/requires-port": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/karma-safari-launcher/-/karma-safari-launcher-1.0.0.tgz", - "integrity": "sha1-lpgqLMR9BmquccVTursoMZEVos4=", "dev": true, - "requires": {} + "license": "MIT" }, - "karma-teamcity-reporter": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/karma-teamcity-reporter/-/karma-teamcity-reporter-1.1.0.tgz", - "integrity": "sha512-Ca1uhHGtNqUuzsnW3I+QykNuS/jF9vdxnIrkbLCVJRunCc6yWJq+ai1UobQT13j0e3JVUOf0mKo3QHZ6A6mG9Q==", + "node_modules/resolve": { + "version": "1.22.1", "dev": true, - "requires": {} - }, - "keyman": { - "version": "file:web", - "requires": { - "@keymanapp/input-processor": "*", - "@keymanapp/keyboard-processor": "*", - "@keymanapp/keyman-version": "*", - "@keymanapp/lexical-model-layer": "*", - "@keymanapp/models-types": "*", - "@keymanapp/recorder-core": "*", - "@keymanapp/resources-gosh": "*", - "@keymanapp/web-sentry-manager": "*", - "@keymanapp/web-utils": "*", - "@sentry/cli": "2.2.0", - "@types/node": "^11.9.4", - "chai": "^4.3.4", - "eventemitter3": "^4.0.0", - "google-closure-compiler-java": "^20200224.0.0", - "karma": "^6.4.1", - "karma-browserstack-launcher": "^1.6.0", - "karma-chai": "^0.1.0", - "karma-chrome-launcher": "^2.2.0", - "karma-edge-launcher": "^0.4.2", - "karma-firefox-launcher": "^1.1.0", - "karma-fixture": "^0.2.6", - "karma-html2js-preprocessor": "^1.1.0", - "karma-ie-launcher": "^1.0.0", - "karma-json-fixtures-preprocessor": "0.0.6", - "karma-mocha": "^2.0.1", - "karma-mocha-reporter": "^2.2.5", - "karma-safari-launcher": "^1.0.0", - "karma-teamcity-reporter": "^1.1.0", - "mocha": "^10.0.0", - "modernizr": "^3.11.7", - "ts-node": "^10.9.1", - "typescript": "^4.9.5" + "license": "MIT", + "dependencies": { + "is-core-module": "^2.9.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" } }, - "keyv": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.2.2.tgz", - "integrity": "sha512-uYS0vKTlBIjNCAUqrjlxmruxOEiZxZIHXyp32sdcGmP+ukFrmWUnE//RcPXJH3Vxrni1H2gsQbjHE0bH7MtMQQ==", - "requires": { - "compress-brotli": "^1.3.6", - "json-buffer": "3.0.1" - } + "node_modules/resolve-alpn": { + "version": "1.2.1", + "license": "MIT" }, - "lie": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", - "integrity": "sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==", - "dev": true, - "requires": { - "immediate": "~3.0.5" + "node_modules/responselike": { + "version": "2.0.0", + "license": "MIT", + "dependencies": { + "lowercase-keys": "^2.0.0" } }, - "linkify-it": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", - "integrity": "sha512-ynTsyrFSdE5oZ/O9GEf00kPngmOfVwazR5GKDq6EYfhlpFug3J2zybX56a2PRRpc9P+FuSoGNAwjlbDs9jJBPQ==", - "dev": true, - "requires": { - "uc.micro": "^1.0.1" + "node_modules/restructure": { + "version": "3.0.0", + "resolved": "git+ssh://git@github.com/keymanapp/dependency-restructure.git#49d129cf0916d082a7278bb09296fb89cecfcc50", + "integrity": "sha512-2vBFOLc0+dkUb3Pnpl87o+B4SIc3MAm3P8PXT8YFkP51URFGsgTgm3gAnwTX2jXSEd/5eT9n7C733loG6OaVgw==", + "license": "MIT" + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "optional": true, + "engines": { + "node": ">= 4" } }, - "locate-path": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", - "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "node_modules/rfdc": { + "version": "1.3.0", "dev": true, - "requires": { - "p-locate": "^5.0.0" - } + "license": "MIT" }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", - "dev": true + "node_modules/rimraf": { + "version": "3.0.2", + "devOptional": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } }, - "lodash.clonedeep": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", - "integrity": "sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=" + "node_modules/safe-buffer": { + "version": "5.2.1", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + "node_modules/safer-buffer": { + "version": "2.1.2", + "license": "MIT" }, - "lodash.set": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", - "integrity": "sha512-4hNPN5jlm/N/HLMCO43v8BXKq9Z7QdAGc/VGrRD61w8gN9g/6jF9A4L1pbUgBLCffi0w9VsXfTOij5x8iTyFvg==" + "node_modules/sax": { + "version": "1.2.4", + "license": "ISC" }, - "lodash.uniq": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", - "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" + "node_modules/semver": { + "version": "7.3.8", + "license": "ISC", + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } }, - "log4js": { - "version": "6.4.4", - "resolved": "https://registry.npmjs.org/log4js/-/log4js-6.4.4.tgz", - "integrity": "sha512-ncaWPsuw9Vl1CKA406hVnJLGQKy1OHx6buk8J4rE2lVW+NW5Y82G5/DIloO7NkqLOUtNPEANaWC1kZYVjXssPw==", - "dev": true, - "requires": { - "date-format": "^4.0.6", - "debug": "^4.3.4", - "flatted": "^3.2.5", - "rfdc": "^1.3.0", - "streamroller": "^3.0.6" + "node_modules/send": { + "version": "0.17.2", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "1.8.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.3.0", + "range-parser": "~1.2.1", + "statuses": "~1.5.0" }, - "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } + "engines": { + "node": ">= 0.8.0" } }, - "lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", - "dev": true + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } }, - "lowercase-keys": { + "node_modules/send/node_modules/debug/node_modules/ms": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" - }, - "lru_map": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.3.3.tgz", - "integrity": "sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0=" + "license": "MIT" }, - "lru-cache": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.8.1.tgz", - "integrity": "sha512-E1v547OCgJvbvevfjgK9sNKIVXO96NnsTsFPBlg4ZxjhsJSODoH9lk8Bm0OxvHNm6Vm5Yqkl/1fErDxhYL8Skg==", - "devOptional": true - }, - "macos-release": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/macos-release/-/macos-release-2.5.0.tgz", - "integrity": "sha512-EIgv+QZ9r+814gjJj0Bt5vSLJLzswGmSUbUpbi9AIr/fsN2IWFBl2NucV9PAiek+U1STK468tEkxmVYUtuAN3g==" - }, - "make-error": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", - "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", - "dev": true + "node_modules/send/node_modules/mime": { + "version": "1.6.0", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } }, - "map-stream": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/map-stream/-/map-stream-0.1.0.tgz", - "integrity": "sha1-5WqpTEyAVaFkBKBnS3jyFffI4ZQ=", - "dev": true + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "license": "MIT" }, - "markdown-it": { - "version": "12.3.2", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", - "integrity": "sha512-TchMembfxfNVpHkbtriWltGWc+m3xszaRD0CZup7GFFhzIgQqxIfn3eGj1yZpfuflzPvfkt611B2Q/Bsk1YnGg==", + "node_modules/serialize-javascript": { + "version": "5.0.1", "dev": true, - "requires": { - "argparse": "^2.0.1", - "entities": "~2.1.0", - "linkify-it": "^3.0.1", - "mdurl": "^1.0.1", - "uc.micro": "^1.0.5" + "license": "BSD-3-Clause", + "dependencies": { + "randombytes": "^2.1.0" } }, - "mdurl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-1.0.1.tgz", - "integrity": "sha1-/oWy7HWlkDfyrf7BAP1sYBdhFS4=", - "dev": true + "node_modules/serve-static": { + "version": "1.14.2", + "license": "MIT", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.17.2" + }, + "engines": { + "node": ">= 0.8.0" + } }, - "media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + "node_modules/set-blocking": { + "version": "2.0.0", + "license": "ISC" }, - "merge-descriptors": { + "node_modules/set-immediate-shim": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" - }, - "methods": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true + "node_modules/setprototypeof": { + "version": "1.2.0", + "license": "ISC" }, - "mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + "node_modules/shebang-command": { + "version": "2.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } }, - "mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "requires": { - "mime-db": "1.52.0" + "node_modules/shebang-regex": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "mimic-response": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", - "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" + "node_modules/shelljs": { + "version": "0.8.5", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + }, + "bin": { + "shjs": "bin/shjs" + }, + "engines": { + "node": ">=4" + } }, - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "devOptional": true, - "requires": { - "brace-expansion": "^1.1.7" + "node_modules/shelljs.exec": { + "version": "1.1.8", + "dev": true, + "license": "ISC", + "engines": { + "node": ">= 4.0.0" } }, - "minimist": { - "version": "1.2.6", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz", - "integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==" + "node_modules/signal-exit": { + "version": "3.0.7", + "license": "ISC" }, - "minipass": { - "version": "3.1.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.1.6.tgz", - "integrity": "sha512-rty5kpw9/z8SX9dmxblFA6edItUmwJgMeYDZRrwlIVN27i8gysGbznJwUggw2V/FVqFSDdWy040ZPS811DYAqQ==", - "optional": true, - "requires": { - "yallist": "^4.0.0" + "node_modules/sinon": { + "version": "7.5.0", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "@sinonjs/commons": "^1.4.0", + "@sinonjs/formatio": "^3.2.1", + "@sinonjs/samsam": "^3.3.3", + "diff": "^3.5.0", + "lolex": "^4.2.0", + "nise": "^1.5.2", + "supports-color": "^5.5.0" } }, - "minizlib": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", - "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", - "optional": true, - "requires": { - "minipass": "^3.0.0", - "yallist": "^4.0.0" + "node_modules/sinon/node_modules/diff": { + "version": "3.5.0", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" } }, - "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" + "node_modules/sinon/node_modules/has-flag": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" } }, - "mocha": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.2.0.tgz", - "integrity": "sha512-IDY7fl/BecMwFHzoqF2sg/SHHANeBoMMXFlS9r0OXKDssYE1M5O43wUY/9BVPeIvfH2zmEbBfseqN9gBQZzXkg==", + "node_modules/sinon/node_modules/supports-color": { + "version": "5.5.0", "dev": true, - "requires": { - "ansi-colors": "4.1.1", - "browser-stdout": "1.3.1", - "chokidar": "3.5.3", - "debug": "4.3.4", - "diff": "5.0.0", - "escape-string-regexp": "4.0.0", - "find-up": "5.0.0", - "glob": "7.2.0", - "he": "1.2.0", - "js-yaml": "4.1.0", - "log-symbols": "4.1.0", - "minimatch": "5.0.1", - "ms": "2.1.3", - "nanoid": "3.3.3", - "serialize-javascript": "6.0.0", - "strip-json-comments": "3.1.1", - "supports-color": "8.1.1", - "workerpool": "6.2.1", - "yargs": "16.2.0", - "yargs-parser": "20.2.4", - "yargs-unparser": "2.0.0" - }, + "license": "MIT", "dependencies": { - "chokidar": { - "version": "3.5.3", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", - "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", - "dev": true, - "requires": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "fsevents": "~2.3.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - } - }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true - } - } - }, - "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 - }, - "glob": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", - "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", - "dev": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "dependencies": { - "minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", - "dev": true, - "requires": { - "brace-expansion": "^1.1.7" - } - } - } - }, - "log-symbols": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", - "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", - "dev": true, - "requires": { - "chalk": "^4.1.0", - "is-unicode-supported": "^0.1.0" - } - }, - "minimatch": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz", - "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==", - "dev": true, - "requires": { - "brace-expansion": "^2.0.1" - }, - "dependencies": { - "brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0" - } - } - } - }, - "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 - }, - "nanoid": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz", - "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==", - "dev": true - }, - "readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "requires": { - "picomatch": "^2.2.1" - } - }, - "serialize-javascript": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz", - "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==", - "dev": true, - "requires": { - "randombytes": "^2.1.0" - } - }, - "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, - "requires": { - "has-flag": "^4.0.0" - } - }, - "workerpool": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz", - "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==", - "dev": true - } + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" } }, - "mocha-teamcity-reporter": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/mocha-teamcity-reporter/-/mocha-teamcity-reporter-4.0.0.tgz", - "integrity": "sha512-m+ADz1U7JNf3od4HrY5t4xAKIDamj1171WJCb8nrSuF+RIqP+AP4gq6UJ+U9nPVn2XO8y1nSs5bQ9Kxzelz6Nw==", - "dev": true, - "requires": {} + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } }, - "modernizr": { - "version": "3.12.0", - "resolved": "https://registry.npmjs.org/modernizr/-/modernizr-3.12.0.tgz", - "integrity": "sha512-i5f5xfnxMOb3cePoBpwk4bWjVAyIB3hgm7QrDvZx/R7zUUS8PO9zlyQF7vJKn8kCVxEvL0nRWeZ0PPqVbY31sw==", + "node_modules/socket.io": { + "version": "4.4.1", "dev": true, - "requires": { - "doctrine": "^3.0.0", - "file": "^0.2.2", - "lodash": "^4.17.21", - "markdown-it": "^12.3.2", - "mkdirp": "0.5.5", - "requirejs": "^2.3.6", - "yargs": "^15.4.1" - }, + "license": "MIT", "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "dev": true, - "requires": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "requires": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "requires": { - "p-locate": "^4.1.0" - } - }, - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "dev": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "requires": { - "p-limit": "^2.2.0" - } - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - }, - "y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "dev": true - }, - "yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "dev": true, - "requires": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "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" - } - }, - "yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "dev": true, - "requires": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - } - } + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "debug": "~4.3.2", + "engine.io": "~6.1.0", + "socket.io-adapter": "~2.3.3", + "socket.io-parser": "~4.0.4" + }, + "engines": { + "node": ">=10.0.0" } }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==" + "node_modules/socket.io-adapter": { + "version": "2.3.3", + "dev": true, + "license": "MIT" }, - "ngrok": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ngrok/-/ngrok-4.3.1.tgz", - "integrity": "sha512-s0joO2liKYiGTVARyzL8hfLIXAZT03GDK3oJqsZK6d61Es+HCx77j8E9ysUbtkMEyvBgYmIMr8taQNsvQt4/DQ==", - "requires": { - "@types/node": "^8.10.50", - "extract-zip": "^2.0.1", - "got": "^11.5.1", - "hpagent": "^0.1.2", - "lodash.clonedeep": "^4.5.0", - "uuid": "^7.0.0 || ^8.0.0", - "yaml": "^1.10.0" + "node_modules/socket.io-parser": { + "version": "4.0.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/component-emitter": "^1.2.10", + "component-emitter": "~1.3.0", + "debug": "~4.3.1" }, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/socket.io/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "license": "MIT", "dependencies": { - "@types/node": { - "version": "8.10.66", - "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", - "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==" + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + "node_modules/socks": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.7.1.tgz", + "integrity": "sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==", + "optional": true, + "dependencies": { + "ip": "^2.0.0", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.13.0", + "npm": ">= 3.0.0" + } }, - "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", - "dev": true, - "requires": { - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/text-encoding": "^0.7.1", - "just-extend": "^4.0.2", - "lolex": "^5.0.1", - "path-to-regexp": "^1.7.0" + "node_modules/socks-proxy-agent": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz", + "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/socks-proxy-agent/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "optional": true, "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, - "path-to-regexp": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", - "integrity": "sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==", - "dev": true, - "requires": { - "isarray": "0.0.1" - } + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "node-addon-api": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", - "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", - "optional": true + "node_modules/source-map": { + "version": "0.6.1", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } }, - "node-cleanup": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", - "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=", - "dev": true + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } }, - "node-fetch": { - "version": "2.6.7", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", - "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", - "requires": { - "whatwg-url": "^5.0.0" + "node_modules/split": { + "version": "0.3.3", + "dev": true, + "license": "MIT", + "dependencies": { + "through": "2" + }, + "engines": { + "node": "*" } }, - "node-gyp": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-7.1.2.tgz", - "integrity": "sha512-CbpcIo7C3eMu3dL1c3d0xw449fHIGALIJsRP4DDPHpyiW8vcriNY7ubh9TE4zEKfSxscY7PjeFnshE7h75ynjQ==", + "node_modules/ssri": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz", + "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==", "optional": true, - "requires": { - "env-paths": "^2.2.0", - "glob": "^7.1.4", - "graceful-fs": "^4.2.3", - "nopt": "^5.0.0", - "npmlog": "^4.1.2", - "request": "^2.88.2", - "rimraf": "^3.0.2", - "semver": "^7.3.2", - "tar": "^6.0.2", - "which": "^2.0.2" + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node-windows-trayicon": { - "version": "git+ssh://git@github.com/keymanapp/node-windows-trayicon.git#1e46786082213f3edcddd5953e33f5abdc7ea05f", - "from": "node-windows-trayicon@git+ssh://git@github.com/keymanapp/node-windows-trayicon.git#1e46786082213f3edcddd5953e33f5abdc7ea05f", - "optional": true, - "requires": { - "bindings": "^1.5.0", - "node-addon-api": "^3.1.0", - "node-gyp": "^7.1.2" + "node_modules/statuses": { + "version": "1.5.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, - "noms": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/noms/-/noms-0.0.0.tgz", - "integrity": "sha1-2o69nzr51nYJGbJ9nNyAkqczKFk=", + "node_modules/stream-combiner": { + "version": "0.0.4", "dev": true, - "requires": { - "inherits": "^2.0.1", - "readable-stream": "~1.0.31" + "license": "MIT", + "dependencies": { + "duplexer": "~0.1.1" + } + }, + "node_modules/streamroller": { + "version": "3.0.6", + "dev": true, + "license": "MIT", + "dependencies": { + "date-format": "^4.0.6", + "debug": "^4.3.4", + "fs-extra": "^10.0.1" }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/streamroller/node_modules/debug": { + "version": "4.3.4", + "dev": true, + "license": "MIT", "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "0.0.1", - "string_decoder": "~0.10.x" - } - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "nopt": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", - "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", - "optional": true, - "requires": { - "abbrev": "1" + "node_modules/string_decoder": { + "version": "1.1.1", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" } }, - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "license": "MIT" }, - "normalize-url": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" + "node_modules/string-argv": { + "version": "0.1.2", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } }, - "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", - "requires": { - "path-key": "^2.0.0" - }, + "node_modules/string-width": { + "version": "1.0.2", + "dev": true, + "license": "MIT", "dependencies": { - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" - } + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "npmlog": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", - "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" + "node_modules/string.prototype.codepointat": { + "version": "0.2.1", + "license": "MIT" + }, + "node_modules/string.prototype.startswith": { + "version": "0.2.0" + }, + "node_modules/strip-ansi": { + "version": "3.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" } }, - "null-check": { + "node_modules/strip-eof": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/null-check/-/null-check-1.0.0.tgz", - "integrity": "sha1-l33/1xdgErnsMNKjnbXPcqBDnt0=", - "dev": true + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "optional": true + "node_modules/strip-json-comments": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "optional": true + "node_modules/supports-color": { + "version": "7.2.0", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } }, - "octokit-pagination-methods": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/octokit-pagination-methods/-/octokit-pagination-methods-1.1.0.tgz", - "integrity": "sha512-fZ4qZdQ2nxJvtcasX7Ghl+WlWS/d9IgnBIwFZXVNNZUmzpno91SX5bc5vuxiuKoCtK78XxGGNuSCrDC7xYB3OQ==" + "node_modules/tar": { + "version": "6.1.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.13.tgz", + "integrity": "sha512-jdIBIN6LTIe2jqzay/2vtYLlBHa3JF42ot3h1dW8Q0PaAG4v8rm0cvpVePtau5C6OKXGGcgO9q2AMNSWxiLqKw==", + "optional": true, + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^4.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" + "node_modules/tar/node_modules/minipass": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-4.0.3.tgz", + "integrity": "sha512-OW2r4sQ0sI+z5ckEt5c1Tri4xTgZwYDxpE54eqWlQloQRoWtXjqt9udJ5Z4dSv7wK+nfFI7FRXyCpBSft+gpFw==", + "optional": true, + "engines": { + "node": ">=8" } }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" } }, - "open": { - "version": "8.4.0", - "resolved": "https://registry.npmjs.org/open/-/open-8.4.0.tgz", - "integrity": "sha512-XgFPPM+B28FtCCgSb9I+s9szOC1vZRSwgWsRUA5ylIxRTgKozqjOCrVOqGsYABPYK5qnfqClxZTFBa8PKt2v6Q==", - "requires": { - "define-lazy-prop": "^2.0.0", - "is-docker": "^2.1.1", - "is-wsl": "^2.2.0" + "node_modules/temp-fs": { + "version": "0.9.9", + "dev": true, + "license": "MIT", + "dependencies": { + "rimraf": "~2.5.2" + }, + "engines": { + "node": ">=0.8.0" } }, - "os-name": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/os-name/-/os-name-3.1.0.tgz", - "integrity": "sha512-h8L+8aNjNcMpo/mAIBPn5PXCM16iyPGjHNWo6U1YO8sJTMHtEtyczI6QJnLoplswm6goopQkqc7OAnjhWcugVg==", - "requires": { - "macos-release": "^2.2.0", - "windows-release": "^3.1.0" + "node_modules/temp-fs/node_modules/rimraf": { + "version": "2.5.4", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.0.5" + }, + "bin": { + "rimraf": "bin.js" } }, - "p-cancelable": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" + "node_modules/test-exclude": { + "version": "6.0.0", + "dev": true, + "license": "ISC", + "dependencies": { + "@istanbuljs/schema": "^0.1.2", + "glob": "^7.1.4", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=8" + } }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" + "node_modules/through": { + "version": "2.3.8", + "dev": true, + "license": "MIT" }, - "p-limit": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", - "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "node_modules/through2": { + "version": "2.0.5", "dev": true, - "requires": { - "yocto-queue": "^0.1.0" + "license": "MIT", + "dependencies": { + "readable-stream": "~2.3.6", + "xtend": "~4.0.1" } }, - "p-locate": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", - "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "node_modules/tmp": { + "version": "0.2.1", "dev": true, - "requires": { - "p-limit": "^3.0.2" + "license": "MIT", + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" } }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "pako": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", - "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", - "dev": true - }, - "parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==" - }, - "path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + "node_modules/to-regex-range": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } }, - "path-is-absolute": { + "node_modules/toidentifier": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "devOptional": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true - }, - "path-to-regexp": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + "license": "MIT", + "engines": { + "node": ">=0.6" + } }, - "pathval": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", - "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", - "dev": true + "node_modules/tr46": { + "version": "0.0.3", + "license": "MIT" }, - "pause-stream": { - "version": "0.0.11", - "resolved": "https://registry.npmjs.org/pause-stream/-/pause-stream-0.0.11.tgz", - "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", + "node_modules/ts-node": { + "version": "10.9.1", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", + "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", "dev": true, - "requires": { - "through": "~2.3" + "dependencies": { + "@cspotcode/source-map-support": "^0.8.0", + "@tsconfig/node10": "^1.0.7", + "@tsconfig/node12": "^1.0.7", + "@tsconfig/node14": "^1.0.0", + "@tsconfig/node16": "^1.0.2", + "acorn": "^8.4.1", + "acorn-walk": "^8.1.1", + "arg": "^4.1.0", + "create-require": "^1.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "v8-compile-cache-lib": "^3.0.1", + "yn": "3.1.1" + }, + "bin": { + "ts-node": "dist/bin.js", + "ts-node-cwd": "dist/bin-cwd.js", + "ts-node-esm": "dist/bin-esm.js", + "ts-node-script": "dist/bin-script.js", + "ts-node-transpile-only": "dist/bin-transpile.js", + "ts-script": "dist/bin-script-deprecated.js" + }, + "peerDependencies": { + "@swc/core": ">=1.2.50", + "@swc/wasm": ">=1.2.50", + "@types/node": "*", + "typescript": ">=2.7" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "@swc/wasm": { + "optional": true + } } }, - "pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "optional": true - }, - "picomatch": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.3.tgz", - "integrity": "sha512-KpELjfwcCDUb9PeigTs2mBJzXUPzAuP2oPcA989He8Rte0+YUAjw1JVedDhuTKPkHjSYzMN3npC9luThGYEKdg==", - "dev": true - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" - }, - "progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "dev": true + "node_modules/ts-node/node_modules/diff": { + "version": "4.0.2", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.3.1" + } }, - "proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", - "requires": { - "forwarded": "0.2.0", - "ipaddr.js": "1.9.1" + "node_modules/tsc-watch": { + "version": "4.6.2", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "node-cleanup": "^2.1.2", + "ps-tree": "^1.2.0", + "string-argv": "^0.1.1", + "strip-ansi": "^6.0.0" + }, + "bin": { + "tsc-watch": "index.js" + }, + "engines": { + "node": ">=8.17.0" + }, + "peerDependencies": { + "typescript": "*" } }, - "proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true + "node_modules/tsc-watch/node_modules/ansi-regex": { + "version": "5.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "ps-tree": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/ps-tree/-/ps-tree-1.2.0.tgz", - "integrity": "sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==", + "node_modules/tsc-watch/node_modules/strip-ansi": { + "version": "6.0.1", "dev": true, - "requires": { - "event-stream": "=3.3.4" + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "optional": true + "node_modules/tslib": { + "version": "1.14.1", + "license": "0BSD" }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "node_modules/tunnel": { + "version": "0.0.6", + "license": "MIT", + "engines": { + "node": ">=0.6.11 <=0.7.0 || >=0.7.3" } }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "optional": true - }, - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=", - "dev": true + "node_modules/type-detect": { + "version": "4.0.8", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } }, - "qjobs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/qjobs/-/qjobs-1.2.0.tgz", - "integrity": "sha512-8YOJEHtxpySA3fFDyCRxA+UUV+fA+rTWnuWvylOK/NCjhY+b4ocCtmu8TtsWb+mYeU+GCHf/S66KZF/AsteKHg==", - "dev": true + "node_modules/type-is": { + "version": "1.6.18", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } }, - "qs": { - "version": "6.9.7", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.9.7.tgz", - "integrity": "sha512-IhMFgUmuNpyRfxA90umL7ByLlgRXu6tIfKPpF5TmcfRLlLCckfP/g3IQmju6jjpu+Hh8rA+2p6A27ZSPOOHdKw==" + "node_modules/typedarray": { + "version": "0.0.6", + "license": "MIT" }, - "quick-lru": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", - "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } }, - "randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "node_modules/ua-parser-js": { + "version": "0.7.31", "dev": true, - "requires": { - "safe-buffer": "^5.1.0" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "license": "MIT", + "engines": { + "node": "*" } }, - "range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==" - }, - "raw-body": { - "version": "2.4.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.4.3.tgz", - "integrity": "sha512-UlTNLIcu0uzb4D2f4WltY6cVjLi+/jEN4lgEUj3E04tpMDpUlkBo/eSn6zou9hum2VMNpCCUone0O0WeJim07g==", - "requires": { - "bytes": "3.1.2", - "http-errors": "1.8.1", - "iconv-lite": "0.4.24", - "unpipe": "1.0.0" - } + "node_modules/uc.micro": { + "version": "1.0.6", + "dev": true, + "license": "MIT" }, - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" + "node_modules/unique-filename": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz", + "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==", + "optional": true, + "dependencies": { + "unique-slug": "^3.0.0" }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/unique-slug": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz", + "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==", + "optional": true, "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } + "imurmurhash": "^0.1.4" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "readdirp": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.5.0.tgz", - "integrity": "sha512-cMhu7c/8rdhkHXWsY+osBhfSy0JikwpHK/5+imo+LpeasTF8ouErHrlYkwT0++njiyuDvc7OFY5T3ukvZ8qmFQ==", + "node_modules/universal-user-agent": { + "version": "6.0.0", + "license": "ISC" + }, + "node_modules/universalify": { + "version": "2.0.0", "dev": true, - "requires": { - "picomatch": "^2.2.1" + "license": "MIT", + "engines": { + "node": ">= 10.0.0" } }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "optional": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "qs": { - "version": "6.5.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.3.tgz", - "integrity": "sha512-qxXIEh4pCGfHICj1mAJQ2/2XVZkjCDTcEgfoSQxc/fYivUZxTkk7L3bDBJSoNrEzXI17oUO5Dp07ktqE5KzczA==", - "optional": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "optional": true - } + "node_modules/unpipe": { + "version": "1.0.0", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" + "node_modules/untildify": { + "version": "4.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + "node_modules/uri-js": { + "version": "4.4.1", + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } }, - "requirejs": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", - "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", - "dev": true + "node_modules/util-deprecate": { + "version": "1.0.2", + "license": "MIT" }, - "requires-port": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", - "dev": true + "node_modules/utils-merge": { + "version": "1.0.1", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } }, - "resolve-alpn": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", - "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" + "node_modules/uuid": { + "version": "8.3.2", + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } }, - "responselike": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", - "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", - "requires": { - "lowercase-keys": "^2.0.0" + "node_modules/v8-compile-cache-lib": { + "version": "3.0.1", + "dev": true, + "license": "MIT" + }, + "node_modules/v8-to-istanbul": { + "version": "9.0.1", + "dev": true, + "license": "ISC", + "dependencies": { + "@jridgewell/trace-mapping": "^0.3.12", + "@types/istanbul-lib-coverage": "^2.0.1", + "convert-source-map": "^1.6.0" + }, + "engines": { + "node": ">=10.12.0" } }, - "rfdc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz", - "integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==", - "dev": true + "node_modules/v8-to-istanbul/node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "devOptional": true, - "requires": { - "glob": "^7.1.3" + "node_modules/vary": { + "version": "1.1.2", + "license": "MIT", + "engines": { + "node": ">= 0.8" } }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + "node_modules/void-elements": { + "version": "2.0.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" + "node_modules/webidl-conversions": { + "version": "3.0.1", + "license": "BSD-2-Clause" }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + "node_modules/whatwg-url": { + "version": "5.0.0", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } }, - "semver": { - "version": "7.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.6.tgz", - "integrity": "sha512-HZWqcgwLsjaX1HBD31msI/rXktuIhS+lWvdE4kN9z+8IVT4Itc7vqU2WvYsyD6/sjYCt4dEKH/m1M3dwI9CC5w==", + "node_modules/which": { + "version": "2.0.2", "devOptional": true, - "requires": { - "lru-cache": "^7.4.0" + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "send": { - "version": "0.17.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.17.2.tgz", - "integrity": "sha512-UJYB6wFSJE3G00nEivR5rgWp8c2xXvJ3OPWPhmuteU0IKj8nKbG3DrjiOmLwpnHGYWAVwA69zmTm++YG0Hmwww==", - "requires": { - "debug": "2.6.9", - "depd": "~1.1.2", - "destroy": "~1.0.4", - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "0.5.2", - "http-errors": "1.8.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.3.0", - "range-parser": "~1.2.1", - "statuses": "~1.5.0" - }, + "node_modules/which-module": { + "version": "2.0.0", + "license": "ISC" + }, + "node_modules/wide-align": { + "version": "1.1.3", + "dev": true, + "license": "ISC", "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - }, - "dependencies": { - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" - } - } - }, - "mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==" - }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - } + "string-width": "^1.0.2 || 2" } }, - "serve-static": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.14.2.tgz", - "integrity": "sha512-+TMNA9AFxUEGuC0z2mevogSnn9MXKb4fa7ngeRMJaaGv8vTwnIEkKi+QGvPt33HSnf8pRS+WGM0EbMtCJLKMBQ==", - "requires": { - "encodeurl": "~1.0.2", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "0.17.2" + "node_modules/windows-release": { + "version": "3.3.3", + "license": "MIT", + "dependencies": { + "execa": "^1.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" - }, - "set-immediate-shim": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz", - "integrity": "sha1-SysbJ+uAip+NzEgaWOXlb1mfP2E=", - "dev": true + "node_modules/workerpool": { + "version": "6.1.0", + "dev": true, + "license": "Apache-2.0" }, - "setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + "node_modules/wrap-ansi": { + "version": "7.0.0", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "5.0.1", "dev": true, - "requires": { - "shebang-regex": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "shebang-regex": { + "node_modules/wrap-ansi/node_modules/is-fullwidth-code-point": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true - }, - "signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "4.2.2", "dev": true, - "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "6.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.5.0", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } + "utf-8-validate": { + "optional": true } } }, - "socket.io": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.6.0.tgz", - "integrity": "sha512-b65bp6INPk/BMMrIgVvX12x3Q+NqlGqSlTuvKQWt0BUJ3Hyy3JangBl7fEoWZTXbOKlCqNPbQ6MbWgok/km28w==", - "dev": true, - "requires": { - "accepts": "~1.3.4", - "base64id": "~2.0.0", - "debug": "~4.3.2", - "engine.io": "~6.4.0", - "socket.io-adapter": "~2.5.2", - "socket.io-parser": "~4.2.1" - }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "git+ssh://git@github.com/keymanapp/dependency-node-xml2js.git#535fe732dc408d697e0f847c944cc45f0baf0829", + "integrity": "sha512-5CS+yWxp0qg8zO7ng/iXrBZm2FXgpiJ+RJ3E0LzRxDXChESqzazRwzl8sXYe8/7je/NfwG4EXcClaRKUkcIvtQ==", + "license": "MIT", "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" } }, - "socket.io-adapter": { - "version": "2.5.2", - "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.2.tgz", - "integrity": "sha512-87C3LO/NOMc+eMcpcxUBebGjkpMDkNBS9tf7KJqcDsmL936EChtVva71Dw2q4tQcuVC+hAUy4an2NO/sYXmwRA==", - "dev": true, - "requires": { - "ws": "~8.11.0" + "node_modules/xmlbuilder": { + "version": "11.0.1", + "license": "MIT", + "engines": { + "node": ">=4.0" } }, - "socket.io-parser": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.2.tgz", - "integrity": "sha512-DJtziuKypFkMMHCm2uIshOYC7QaylbtzQwiMYDuCKy3OPkjLzu4B2vAhTlqipRHHzrI0NJeBAizTK7X+6m1jVw==", - "dev": true, - "requires": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.3.1" + "node_modules/xtend": { + "version": "4.0.2", + "license": "MIT", + "engines": { + "node": ">=0.4" } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "split": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/split/-/split-0.3.3.tgz", - "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", + "node_modules/y18n": { + "version": "5.0.8", "dev": true, - "requires": { - "through": "2" + "license": "ISC", + "engines": { + "node": ">=10" } }, - "sshpk": { - "version": "1.17.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", - "integrity": "sha512-/9HIEs1ZXGhSPE8X6Ccm7Nam1z8KcoCqPdI7ecm1N33EzAetWahvQWVqLZtaZQ+IDKX4IyA2o0gBzqIMkAagHQ==", - "optional": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + "node_modules/yallist": { + "version": "4.0.0", + "license": "ISC" }, - "stream-combiner": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/stream-combiner/-/stream-combiner-0.0.4.tgz", - "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", - "dev": true, - "requires": { - "duplexer": "~0.1.1" + "node_modules/yaml": { + "version": "1.10.2", + "license": "ISC", + "engines": { + "node": ">= 6" } }, - "streamroller": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/streamroller/-/streamroller-3.0.6.tgz", - "integrity": "sha512-Qz32plKq/MZywYyhEatxyYc8vs994Gz0Hu2MSYXXLD233UyPeIeRBZARIIGwFer4Mdb8r3Y2UqKkgyDghM6QCg==", + "node_modules/yargs": { + "version": "16.2.0", "dev": true, - "requires": { - "date-format": "^4.0.6", - "debug": "^4.3.4", - "fs-extra": "^10.0.1" - }, + "license": "MIT", "dependencies": { - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "dev": true, - "requires": { - "ms": "2.1.2" - } - } - } - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.0", + "y18n": "^5.0.5", + "yargs-parser": "^20.2.2" }, - "dependencies": { - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } - } - }, - "string-argv": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.1.2.tgz", - "integrity": "sha512-mBqPGEOMNJKXRo7z0keX0wlAhbBAjilUdPW13nN0PecVryZxdHIeM7TqbsSUA7VYuS00HGC6mojP7DlQzfa9ZA==", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string.prototype.codepointat": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string.prototype.codepointat/-/string.prototype.codepointat-0.2.1.tgz", - "integrity": "sha512-2cBVCj6I4IOvEnjgO/hWqXjqBGsY+zwPmHl12Srk9IXSZ56Jwwmy+66XO5Iut/oQVR7t5ihYdLB0GMa4alEUcg==" - }, - "string.prototype.startswith": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/string.prototype.startswith/-/string.prototype.startswith-0.2.0.tgz", - "integrity": "sha1-2miYLjU6TprEpDtFCiBF0cRFrns=" - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" + "engines": { + "node": ">=10" } }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha512-7FCwGGmx8mD5xQd3RPUvnSpUXHM3BWuzjtpD4TXsfcZ9EL4azvVVUscFYwD9nx8Kh+uCBC00XBtAykoMHwTh8Q==" - }, - "strip-json-comments": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", - "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" + "node_modules/yargs-parser": { + "version": "20.2.4", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" } }, - "tar": { - "version": "6.1.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz", - "integrity": "sha512-an/KZQzQUkZCkuoAA64hM92X0Urb6VpRhAFllDzz44U2mcD5scmT3zBc4VgVpkugF580+DQn8eAFSyoQt0tznA==", - "optional": true, - "requires": { - "chownr": "^2.0.0", - "fs-minipass": "^2.0.0", - "minipass": "^3.0.0", - "minizlib": "^2.1.1", - "mkdirp": "^1.0.3", - "yallist": "^4.0.0" - }, + "node_modules/yargs-unparser": { + "version": "2.0.0", + "dev": true, + "license": "MIT", "dependencies": { - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", - "optional": true - } + "camelcase": "^6.0.0", + "decamelize": "^4.0.0", + "flat": "^5.0.2", + "is-plain-obj": "^2.1.0" + }, + "engines": { + "node": ">=10" } }, - "temp-fs": { - "version": "0.9.9", - "resolved": "https://registry.npmjs.org/temp-fs/-/temp-fs-0.9.9.tgz", - "integrity": "sha1-gHFzBDeHByDpQxUy/igUNk+IA9c=", + "node_modules/yargs-unparser/node_modules/camelcase": { + "version": "6.2.0", "dev": true, - "requires": { - "rimraf": "~2.5.2" + "license": "MIT", + "engines": { + "node": ">=10" }, - "dependencies": { - "rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", - "dev": true, - "requires": { - "glob": "^7.0.5" - } - } + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", - "integrity": "sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==", + "node_modules/yargs-unparser/node_modules/decamelize": { + "version": "4.0.0", "dev": true, - "requires": { - "readable-stream": "~2.3.6", - "xtend": "~4.0.1" + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "tmp": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", - "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "node_modules/yargs-unparser/node_modules/is-plain-obj": { + "version": "2.1.0", "dev": true, - "requires": { - "rimraf": "^3.0.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "to-regex-range": { + "node_modules/yargs/node_modules/ansi-regex": { "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "dev": true, - "requires": { - "is-number": "^7.0.0" + "license": "MIT", + "engines": { + "node": ">=8" } }, - "toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==" - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" + "node_modules/yargs/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o=" - }, - "ts-node": { - "version": "10.9.1", - "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz", - "integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==", + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.2", "dev": true, - "requires": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, + "license": "MIT", "dependencies": { - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", - "dev": true - } + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, - "tsc-watch": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/tsc-watch/-/tsc-watch-4.6.2.tgz", - "integrity": "sha512-eHWzZGkPmzXVGQKbqQgf3BFpGiZZw1jQ29ZOJeaSe8JfyUvphbd221NfXmmsJUGGPGA/nnaSS01tXipUcyxAxg==", + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.0", "dev": true, - "requires": { - "cross-spawn": "^7.0.3", - "node-cleanup": "^2.1.2", - "ps-tree": "^1.2.0", - "string-argv": "^0.1.1", - "strip-ansi": "^6.0.0" - }, + "license": "MIT", "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } + "ansi-regex": "^5.0.0" + }, + "engines": { + "node": ">=8" } }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" - }, - "tunnel": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", - "integrity": "sha512-1h/Lnq9yajKY2PEbBadPXj3VxsDDu844OnaAo52UVmIzIvwwtBPIuNvkjuzBlTWpfJyUbG3ez0KSBibQkj4ojg==" - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "optional": true, - "requires": { - "safe-buffer": "^5.0.1" + "node_modules/yauzl": { + "version": "2.10.0", + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" } }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true + "node_modules/yn": { + "version": "3.1.1", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true + "node_modules/yocto-queue": { + "version": "0.1.0", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", - "requires": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "resources/build/version": { + "name": "@keymanapp/auto-history-action", + "license": "MIT", + "dependencies": { + "@actions/core": "^1.9.1", + "@actions/github": "^2.1.0", + "typescript": "^4.9.5", + "yargs": "^15.1.0" + }, + "devDependencies": { + "@types/node": "^13.7.0", + "@types/semver": "^7.1.0", + "semver": "^7.1.2", + "ts-node": "^10.9.1" + }, + "engines": { + "node": ">=16.0" } }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=" - }, - "typescript": { - "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==" - }, - "ua-parser-js": { - "version": "0.7.33", - "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.33.tgz", - "integrity": "sha512-s8ax/CeZdK9R/56Sui0WM6y9OFREJarMRHqLB2EwkovemBxNQ+Bqu8GAsUnVcXKgphb++ghr/B2BZx4mahujPw==", - "dev": true + "resources/build/version/node_modules/@types/node": { + "version": "13.13.52", + "dev": true, + "license": "MIT" }, - "uc.micro": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", - "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==", - "dev": true + "resources/build/version/node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "universal-user-agent": { + "resources/build/version/node_modules/cliui": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/universal-user-agent/-/universal-user-agent-6.0.0.tgz", - "integrity": "sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==" - }, - "universalify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", - "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true - }, - "unpipe": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", - "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" - }, - "untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true - }, - "uri-js": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", - "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "optional": true, - "requires": { - "punycode": "^2.1.0" + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" } }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" - }, - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - }, - "v8-compile-cache-lib": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", - "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", - "dev": true - }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "optional": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - }, + "resources/build/version/node_modules/find-up": { + "version": "4.1.0", + "license": "MIT", "dependencies": { - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "optional": true - } + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "void-elements": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/void-elements/-/void-elements-2.0.1.tgz", - "integrity": "sha1-wGavtYK7HLQSjWDqkjkulNXp2+w=", - "dev": true - }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE=" + "resources/build/version/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } }, - "whatwg-url": { + "resources/build/version/node_modules/locate-path": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha1-lmRU6HZUYuN2RNNib2dCzotwll0=", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" } }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "devOptional": true, - "requires": { - "isexe": "^2.0.0" + "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" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=" + "resources/build/version/node_modules/p-locate": { + "version": "4.1.0", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" + "resources/build/version/node_modules/string-width": { + "version": "4.2.3", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "windows-release": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/windows-release/-/windows-release-3.3.3.tgz", - "integrity": "sha512-OSOGH1QYiW5yVor9TtmXKQvt2vjQqbYS+DqmsZw+r7xDwLXEeT3JGW0ZppFmHx4diyXmxt238KFR3N9jzevBRg==", - "requires": { - "execa": "^1.0.0" + "resources/build/version/node_modules/strip-ansi": { + "version": "6.0.1", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "wrap-ansi": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { + "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" }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "ws": { - "version": "8.11.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz", - "integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==", - "requires": {} - }, - "xml2js": { - "version": "0.4.23", - "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", - "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", - "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~11.0.0" + "engines": { + "node": ">=8" } }, - "xmlbuilder": { - "version": "11.0.1", - "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", - "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==" - }, - "xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" - }, - "y18n": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", - "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", - "dev": true - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", - "optional": true - }, - "yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==" + "resources/build/version/node_modules/y18n": { + "version": "4.0.3", + "license": "ISC" }, - "yargs": { - "version": "16.2.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz", - "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==", - "dev": true, - "requires": { - "cliui": "^7.0.2", - "escalade": "^3.1.1", - "get-caller-file": "^2.0.5", + "resources/build/version/node_modules/yargs": { + "version": "15.4.1", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", "string-width": "^4.2.0", - "y18n": "^5.0.5", - "yargs-parser": "^20.2.2" + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } + "engines": { + "node": ">=8" } }, - "yargs-parser": { - "version": "20.2.4", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz", - "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==", - "dev": true - }, - "yargs-unparser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz", - "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==", - "dev": true, - "requires": { - "camelcase": "^6.0.0", - "decamelize": "^4.0.0", - "flat": "^5.0.2", - "is-plain-obj": "^2.1.0" - }, + "resources/build/version/node_modules/yargs-parser": { + "version": "18.1.3", + "license": "ISC", "dependencies": { - "camelcase": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.2.0.tgz", - "integrity": "sha512-c7wVvbw3f37nuobQNtgsgG9POC9qMbNuMQmTCqZv23b6MIz0fcYpBiOlv9gEN/hdLdnZTDQhg6e9Dq5M1vKvfg==", - "dev": true - }, - "decamelize": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz", - "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==", - "dev": true - }, - "is-plain-obj": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", - "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==", - "dev": true - } + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" } }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", - "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" + "resources/gosh": { + "name": "@keymanapp/resources-gosh", + "license": "MIT", + "bin": { + "gosh": "gosh.js" } }, - "yn": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", - "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", - "dev": true - }, - "yocto-queue": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", - "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", - "dev": true + "web": { + "name": "keyman", + "license": "MIT", + "dependencies": { + "@keymanapp/input-processor": "*", + "@keymanapp/keyboard-processor": "*", + "@keymanapp/keyman-version": "*", + "@keymanapp/lexical-model-layer": "*", + "@keymanapp/models-types": "*", + "@keymanapp/recorder-core": "*", + "@keymanapp/web-utils": "*", + "@types/node": "^11.9.4", + "eventemitter3": "^4.0.0" + }, + "devDependencies": { + "@keymanapp/resources-gosh": "*", + "@keymanapp/web-sentry-manager": "*", + "@sentry/cli": "2.2.0", + "chai": "^4.3.4", + "google-closure-compiler-java": "^20200224.0.0", + "karma": "^6.4.1", + "karma-browserstack-launcher": "^1.6.0", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-edge-launcher": "^0.4.2", + "karma-firefox-launcher": "^1.1.0", + "karma-fixture": "^0.2.6", + "karma-html2js-preprocessor": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-json-fixtures-preprocessor": "0.0.6", + "karma-mocha": "^2.0.1", + "karma-mocha-reporter": "^2.2.5", + "karma-safari-launcher": "^1.0.0", + "karma-teamcity-reporter": "^1.1.0", + "mocha": "^10.0.0", + "modernizr": "^3.11.7", + "ts-node": "^10.9.1", + "typescript": "^4.9.5" + } } } } diff --git a/package.json b/package.json index 4cf7efa68e7..f7892d63851 100644 --- a/package.json +++ b/package.json @@ -12,12 +12,24 @@ "workspaces": [ "resources/gosh", "resources/build/version", - "developer/src/kmlmc", + "core/include/ldml", + "developer/src/kmc-keyboard", + "developer/src/kmc-model", + "developer/src/kmc-model-info", + "developer/src/kmc-package", + "developer/src/kmc", "developer/src/server", "common/models/*", "common/tools/*", "common/web/*", "common/predictive-text", + "common/tools/hextobin", "web" - ] + ], + "dependencies": { + "@keymanapp/hextobin": "file:common/tools/hextobin", + "@keymanapp/keyman-version": "file:common/web/keyman-version", + "@keymanapp/common-types": "file:common/web/types", + "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" + } } diff --git a/resources/build/build-utils.sh b/resources/build/build-utils.sh index 32722b10993..8c497b06b00 100755 --- a/resources/build/build-utils.sh +++ b/resources/build/build-utils.sh @@ -335,7 +335,7 @@ run_xcodebuild() { fi } -# + # Sets the BUILDER_OS environment variable to linux|mac|win # _builder_get_operating_system() { @@ -355,3 +355,9 @@ _builder_get_operating_system() { } _builder_get_operating_system + +# +# We always want to use tools out of node_modules/.bin to guarantee that we get the +# correct version +# +set_keyman_standard_build_path diff --git a/resources/build/npm-publish.inc.sh b/resources/build/npm-publish.inc.sh new file mode 100644 index 00000000000..8a178724f37 --- /dev/null +++ b/resources/build/npm-publish.inc.sh @@ -0,0 +1,28 @@ +#!/usr/bin/env bash + + +function npm_publish() { + if [[ $TIER == stable ]]; then + npm_dist_tag=latest + else + npm_dist_tag=$TIER + fi + + set_npm_version + + if builder_has_option --dry-run; then + DRY_RUN=--dry-run + else + DRY_RUN= + fi + + #TEMP: we don't want to publish at this point! + DRY_RUN=--dry-run + + # 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. + # + # See `npm help publish` for more details. + echo "Publishing $DRY_RUN npm package with tag $npm_dist_tag" + npm publish $DRY_RUN --access public --tag $npm_dist_tag || builder_die "Could not publish $npm_dist_tag release." +fi diff --git a/resources/builder.inc.sh b/resources/builder.inc.sh index caed5bea9d0..a8a06d12821 100755 --- a/resources/builder.inc.sh +++ b/resources/builder.inc.sh @@ -922,6 +922,8 @@ builder_parse() { _builder_record_function_call builder_parse + local _builder_params="$@" + _builder_build_deps=--deps builder_verbose= builder_debug= @@ -1064,7 +1066,7 @@ builder_parse() { fi if builder_is_dep_build; then - echo "[$THIS_SCRIPT_IDENTIFIER] dependency build, started by $builder_dep_parent" + echo -e "${HEADING_SETMARK}${COLOR_PURPLE}[$THIS_SCRIPT_IDENTIFIER] dependency build, started by $builder_dep_parent${COLOR_RESET}" if [[ -z ${_builder_deps_built+x} ]]; then echo "FATAL ERROR: Expected --builder-deps-built parameter" exit 1 @@ -1073,6 +1075,7 @@ builder_parse() { # This is a top-level invocation, not a dependency build, so we want to # track which dependencies have been built, so they don't get built multiple # times. + echo -e "${HEADING_SETMARK}${COLOR_PURPLE}[$THIS_SCRIPT_IDENTIFIER] build.sh launched with: <${_builder_params[@]}>${COLOR_RESET}" _builder_deps_built=`mktemp` fi @@ -1247,6 +1250,26 @@ _builder_should_build_dep() { return 0 } +# +# Removes a dependency from the list of available dependencies +# +# Parameters: +# $1 path to dependency +# +builder_remove_dep() { + local dependency="$1" i + dependency="`_builder_expand_relative_path "$dependency"`" + + for i in "${!_builder_deps[@]}"; do + if [[ ${_builder_deps[i]} = $dependency ]]; then + unset '_builder_deps[i]' + fi + done + + # rebuild the array to remove the empty item + _builder_deps=( "${_builder_deps[@]}" ) +} + # # Configure and build all dependencies # Later, may restrict by either action or target diff --git a/resources/standards-data/ldml-keyboards/readme.md b/resources/standards-data/ldml-keyboards/readme.md new file mode 100644 index 00000000000..bcd04b1109b --- /dev/null +++ b/resources/standards-data/ldml-keyboards/readme.md @@ -0,0 +1,24 @@ +# ldml-keyboards + +This data is from https://github.com/unicode-org/cldr.git + +Data will eventually be versioned, so there would be a `42`, `43` etc directory. + +Currently there is a `techpreview` directory referring to the technical preview—work in progress work in 2022. +That will very roughly correspond to , or pull requests thereunto. + +## Data Format + +Each directory contains: + +- `ldmlKeyboard.dtd` - the DTD file +- `ldmlKeyboard.xsd` - the XSD file, automatically converted from the DTD using + Visual Studio, hand tweaked as necessary +- `ldml-keyboard.schema.json` - the JSON schema file, automatically converted + from the XSD using xsd2json (https://github.com/Mermade/jgeXml), hand tweaked + as necessary: + - change toplevel "id" to "$id" + - add "type": "object" to /keyboard + - change /keyboard/keys/key type to "array" + +- `imports/` - the importable data files (TODO-LDML) 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 new file mode 100644 index 00000000000..2c29cfb46be --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/3.0/fr-t-k0-azerty.xml @@ -0,0 +1,175 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/3.0/mt.xml b/resources/standards-data/ldml-keyboards/techpreview/3.0/mt.xml new file mode 100644 index 00000000000..2c5e6a880cb --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/3.0/mt.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json b/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json new file mode 100644 index 00000000000..10924e1ec98 --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/cldr_info.json @@ -0,0 +1,5 @@ +{ + "sha": "d0bdd8868a502afb6c1c1a3e2bf7c3404620f23b", + "description": "release-42-m1-67-gd0bdd8868a", + "date": "Wed, 25 Jan 2023 21:03:15 +0000" +} diff --git a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd new file mode 100644 index 00000000000..8240be771f8 --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.dtd @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.xsd b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.xsd new file mode 100644 index 00000000000..5f6020f51f4 --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboard.xsd @@ -0,0 +1,388 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest.dtd b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest.dtd new file mode 100644 index 00000000000..1b1cff50bd9 --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest.dtd @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest.xsd b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest.xsd new file mode 100644 index 00000000000..b170722b3b6 --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/dtd/ldmlKeyboardTest.xsd @@ -0,0 +1,143 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/fetch-latest-cldr-techpreview.sh b/resources/standards-data/ldml-keyboards/techpreview/fetch-latest-cldr-techpreview.sh new file mode 100755 index 00000000000..d430cd2764c --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/fetch-latest-cldr-techpreview.sh @@ -0,0 +1,78 @@ +#!/usr/bin/env bash + +# Copyright: © SIL International. +# Description: Import new files from CLDR tech preview +# Create Date: 17 Oct 2022 +# Authors: Steven R. Loomis (SRL) + +set -eu + +if [[ $# -ne 1 ]]; +then + echo >&2 "Usage: $0 " + exit 1 +fi + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "$KEYMAN_ROOT/resources/build/jq.inc.sh" + +cd "$THIS_SCRIPT_PATH" + +CLDR_DIR="$1" +shift + +KEYBOARDS_DIR="${CLDR_DIR}/keyboards" +DTD_DIR="${KEYBOARDS_DIR}/dtd" +IMPORT_DIR="${KEYBOARDS_DIR}/import" +DATA_DIR="${KEYBOARDS_DIR}/3.0" +TEST_DIR="${KEYBOARDS_DIR}/test" + +# a file to check +CHECK_1="${DTD_DIR}/ldmlKeyboard.dtd" # Critical, present in prior CLDR +CHECK_2="${DTD_DIR}/ldmlKeyboardTest.dtd" # Only in Keyboard 3.0+ + +if [[ ! -f "${CHECK_1}" ]]; +then + builder_die "${CHECK_1} did not exist: is ${CLDR_DIR} a valid CLDR keyboard directory?" +fi + +if [[ ! -f "${CHECK_2}" ]]; +then + builder_die "${CHECK_2} did not exist: is ${CLDR_DIR} a valid CLDR keyboard directory?" +fi + +# collect git info +GIT_DESCRIBE=$(cd "${CLDR_DIR}" && git describe HEAD || echo unknown) +GIT_SHA=$(cd "${CLDR_DIR}" && git rev-parse HEAD || echo unknown) +NOW=$(date -u -R) + +echo "${CLDR_DIR}" - "${GIT_DESCRIBE}" - "${GIT_SHA}" +echo "---" + +# delete the old files in case some were removed from CLDR +rm -rf ./import ./3.0 ./dtd ./test +# copy over everything +cp -Rv "${IMPORT_DIR}" "${DATA_DIR}" "${DTD_DIR}" "${TEST_DIR}" . + +echo "{\"sha\": \"${GIT_SHA}\",\"description\":\"${GIT_DESCRIBE}\",\"date\":\"${NOW}\"}" | ${JQ} . | tee cldr_info.json +echo "Updated cldr_info.json" + +echo "Converting XSD to JSON…" + +for xsd in dtd/*.xsd; +do + base=$(basename "${xsd}" .xsd | tr A-Z a-z | sed -e 's%^ldml%ldml-%g' ) + json=${base}.schema.json + echo "${xsd} -> ${json}" + (cd .. ; npx -p jgexml xsd2json techpreview/"${xsd}" techpreview/"${json}") || exit + echo 'fixup-schema.js' "${json}" + node fixup-schema.js "${json}" || builder_die "failed to fixup schema ${json}" + mv "${json}" tmp.json + ${JQ} . -S < tmp.json > "${json}" || (rm tmp.json ; builder_die "failed to transform final schema ${json}") + rm tmp.json +done diff --git a/resources/standards-data/ldml-keyboards/techpreview/fixup-schema.js b/resources/standards-data/ldml-keyboards/techpreview/fixup-schema.js new file mode 100644 index 00000000000..c101def788f --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/fixup-schema.js @@ -0,0 +1,64 @@ +/* + Copyright: © SIL International. + Description: Fix up schema from xsd2js + Create Date: 17 Oct 2022 + Authors: Steven R. Loomis (SRL) +*/ + +const { readFileSync, writeFileSync } = require('fs'); +const { argv } = require('process'); + +// Usage: +// node fixup-schema.js [filename.json] +// If no filename, reads and writes stdin/stdout + +// Read stuff +const input = readFileSync(argv[2] || 0, "utf-8"); +const data = JSON.parse(input); + +// Fix stuff +if (!data['$id'] && data['id']) { + data['$id'] = data['id']; + delete data['id']; +} + +/** + * Turn a schema node from an array into a singleton of the specified type + * @param {Object} o + */ +function arrayToSingle(o) { + if (!o) return; + if (o.type === 'array') { + o["$ref"] = o.items["$ref"]; + delete o.items; + delete o.type; + } +} + +/** + * Turn a schema node from a singleton of some type into an array of that type + * @param {Object} o + */ +function singleToArray(o) { + if (!o) return; + if (!o.type) { + o.items = { "$ref": o["$ref"] }; + o.type = "array"; + delete o["$ref"]; + } +} + +if (data.title.endsWith('ldmlKeyboard.xsd')) { + if (data?.properties?.keyboard) { + data.properties.keyboard.type = 'object'; + } + + arrayToSingle(data?.properties?.keyboard?.properties?.vkeys); + singleToArray(data?.definitions?.keys?.properties?.key); + singleToArray(data?.definitions?.keys?.properties?.flicks); + arrayToSingle(data?.definitions?.displays?.properties?.displayOptions); +} + +// Write stuff +const outstr = JSON.stringify(data, null, " "); +writeFileSync(argv[2] || 1, outstr, "utf-8"); diff --git a/resources/standards-data/ldml-keyboards/techpreview/import/README.md b/resources/standards-data/ldml-keyboards/techpreview/import/README.md new file mode 100644 index 00000000000..561b503fc87 --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/import/README.md @@ -0,0 +1,26 @@ +# Keyboard imports + +The XML files in this directory are importable using the keyboard syntax as follows: + +```xml + +``` + +As an experiment, a naming convention is being attempted: + +- _type_`-`_script_`-`_description_`.xml` + +Where _type_ is the top level element such as ``. + +So, + +- `keys-Zyyy-punctuation.xml` is of [script](https://www.unicode.org/iso15924/iso15924-codes.html) `Zyyy`, aka "Common". + + +See [tr35-keyboard.md](../../docs/ldml/tr35-keyboards.md#Element_import) + +## Copyright + +Copyright © 2022 Unicode, Inc. +All rights reserved. +[Terms of use](http://www.unicode.org/copyright.html) diff --git a/resources/standards-data/ldml-keyboards/techpreview/import/keys-Latn-implied.xml b/resources/standards-data/ldml-keyboards/techpreview/import/keys-Latn-implied.xml new file mode 100644 index 00000000000..25f9f0183d3 --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/import/keys-Latn-implied.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/import/keys-Zyyy-punctuation.xml b/resources/standards-data/ldml-keyboards/techpreview/import/keys-Zyyy-punctuation.xml new file mode 100644 index 00000000000..eadad84b53a --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/import/keys-Zyyy-punctuation.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json b/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json new file mode 100644 index 00000000000..93fcc00cfaa --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json @@ -0,0 +1,634 @@ +{ + "$schema": "http://json-schema.org/schema#", + "additionalProperties": false, + "definitions": { + "any": { + "type": "string" + }, + "display": { + "additionalProperties": false, + "properties": { + "display": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "to", + "display" + ], + "type": "object" + }, + "displayOptions": { + "additionalProperties": false, + "properties": { + "baseCharacter": { + "type": "string" + } + }, + "type": "object" + }, + "displays": { + "additionalProperties": false, + "properties": { + "display": { + "items": { + "$ref": "#/definitions/display" + }, + "type": "array" + }, + "displayOptions": { + "$ref": "#/definitions/displayOptions" + }, + "import": { + "items": { + "$ref": "#/definitions/import" + }, + "type": "array" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + } + }, + "type": "object" + }, + "flick": { + "additionalProperties": false, + "properties": { + "directions": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "directions", + "to" + ], + "type": "object" + }, + "flicks": { + "additionalProperties": false, + "properties": { + "flick": { + "items": { + "$ref": "#/definitions/flick" + }, + "minItems": 1, + "type": "array" + }, + "id": { + "type": "string" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + } + }, + "required": [ + "flick", + "id" + ], + "type": "object" + }, + "import": { + "additionalProperties": false, + "properties": { + "base": { + "enum": [ + "cldr" + ], + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "info": { + "additionalProperties": false, + "properties": { + "author": { + "type": "string" + }, + "indicator": { + "type": "string" + }, + "layout": { + "type": "string" + }, + "normalization": { + "type": "string" + } + }, + "type": "object" + }, + "key": { + "additionalProperties": false, + "properties": { + "flicks": { + "type": "string" + }, + "gap": { + "enum": [ + "true" + ], + "type": "string" + }, + "id": { + "type": "string" + }, + "longPress": { + "type": "string" + }, + "longPressDefault": { + "type": "string" + }, + "multiTap": { + "type": "string" + }, + "stretch": { + "enum": [ + "true" + ], + "type": "string" + }, + "switch": { + "type": "string" + }, + "to": { + "type": "string" + }, + "transform": { + "enum": [ + "no" + ], + "type": "string" + }, + "width": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "keys": { + "additionalProperties": false, + "properties": { + "flicks": { + "items": { + "$ref": "#/definitions/flicks" + }, + "type": "array" + }, + "import": { + "items": { + "$ref": "#/definitions/import" + }, + "type": "array" + }, + "key": { + "items": { + "$ref": "#/definitions/key" + }, + "type": "array" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + } + }, + "type": "object" + }, + "layer": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + }, + "modifier": { + "type": "string" + }, + "row": { + "items": { + "$ref": "#/definitions/row" + }, + "minItems": 1, + "type": "array" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + } + }, + "required": [ + "row" + ], + "type": "object" + }, + "layers": { + "additionalProperties": false, + "properties": { + "form": { + "enum": [ + "hardware", + "touch" + ], + "type": "string" + }, + "hardware": { + "type": "string" + }, + "import": { + "items": { + "$ref": "#/definitions/import" + }, + "type": "array" + }, + "layer": { + "items": { + "$ref": "#/definitions/layer" + }, + "type": "array" + }, + "minDeviceWidth": { + "type": "string" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + } + }, + "required": [ + "form" + ], + "type": "object" + }, + "locale": { + "additionalProperties": false, + "properties": { + "id": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "locales": { + "additionalProperties": false, + "properties": { + "locale": { + "items": { + "$ref": "#/definitions/locale" + }, + "type": "array" + } + }, + "type": "object" + }, + "name": { + "additionalProperties": false, + "properties": { + "value": { + "type": "string" + } + }, + "required": [ + "value" + ], + "type": "object" + }, + "names": { + "additionalProperties": false, + "properties": { + "import": { + "items": { + "$ref": "#/definitions/import" + }, + "type": "array" + }, + "name": { + "items": { + "$ref": "#/definitions/name" + }, + "minItems": 1, + "type": "array" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "reorder": { + "additionalProperties": false, + "properties": { + "before": { + "type": "string" + }, + "from": { + "type": "string" + }, + "order": { + "type": "string" + }, + "preBase": { + "type": "string" + }, + "tertiary": { + "type": "string" + }, + "tertiaryBase": { + "type": "string" + } + }, + "required": [ + "from" + ], + "type": "object" + }, + "reorders": { + "additionalProperties": false, + "properties": { + "import": { + "items": { + "$ref": "#/definitions/import" + }, + "type": "array" + }, + "reorder": { + "items": { + "$ref": "#/definitions/reorder" + }, + "type": "array" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + } + }, + "type": "object" + }, + "row": { + "additionalProperties": false, + "properties": { + "keys": { + "type": "string" + } + }, + "required": [ + "keys" + ], + "type": "object" + }, + "settings": { + "additionalProperties": false, + "properties": { + "fallback": { + "enum": [ + "omit" + ], + "type": "string" + }, + "transformFailure": { + "enum": [ + "omit" + ], + "type": "string" + }, + "transformPartial": { + "enum": [ + "hide" + ], + "type": "string" + } + }, + "type": "object" + }, + "special": { + "$ref": "#/definitions/any" + }, + "transform": { + "additionalProperties": false, + "properties": { + "before": { + "type": "string" + }, + "error": { + "enum": [ + "fail" + ], + "type": "string" + }, + "from": { + "type": "string" + }, + "to": { + "type": "string" + } + }, + "required": [ + "from" + ], + "type": "object" + }, + "transforms": { + "additionalProperties": false, + "properties": { + "import": { + "items": { + "$ref": "#/definitions/import" + }, + "type": "array" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + }, + "transform": { + "items": { + "$ref": "#/definitions/transform" + }, + "type": "array" + }, + "type": { + "enum": [ + "simple", + "final", + "backspace" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "version": { + "additionalProperties": false, + "properties": { + "cldrVersion": { + "enum": [ + "techpreview" + ], + "type": "string" + }, + "number": { + "type": "string" + } + }, + "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": { + "keyboard": { + "additionalProperties": false, + "properties": { + "conformsTo": { + "enum": [ + "techpreview" + ], + "type": "string" + }, + "displays": { + "$ref": "#/definitions/displays" + }, + "import": { + "items": { + "$ref": "#/definitions/import" + }, + "type": "array" + }, + "info": { + "$ref": "#/definitions/info" + }, + "keys": { + "$ref": "#/definitions/keys" + }, + "layers": { + "items": { + "$ref": "#/definitions/layers" + }, + "type": "array" + }, + "locale": { + "type": "string" + }, + "locales": { + "$ref": "#/definitions/locales" + }, + "names": { + "$ref": "#/definitions/names" + }, + "reorders": { + "$ref": "#/definitions/reorders" + }, + "settings": { + "$ref": "#/definitions/settings" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + }, + "transforms": { + "items": { + "$ref": "#/definitions/transforms" + }, + "type": "array" + }, + "version": { + "$ref": "#/definitions/version" + }, + "vkeys": { + "$ref": "#/definitions/vkeys" + } + }, + "required": [ + "names", + "locale", + "conformsTo" + ], + "type": "object" + } + }, + "required": [ + "keyboard" + ], + "title": "techpreview/dtd/ldmlKeyboard.xsd", + "type": "object" +} diff --git a/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json b/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json new file mode 100644 index 00000000000..6d163770f9d --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboardtest.schema.json @@ -0,0 +1,221 @@ +{ + "$schema": "http://json-schema.org/schema#", + "additionalProperties": false, + "definitions": { + "any": { + "type": "string" + }, + "backspace": { + "type": "string" + }, + "check": { + "additionalProperties": false, + "properties": { + "result": { + "type": "string" + } + }, + "required": [ + "result" + ], + "type": "object" + }, + "emit": { + "additionalProperties": false, + "properties": { + "to": { + "type": "string" + } + }, + "required": [ + "to" + ], + "type": "object" + }, + "info": { + "additionalProperties": false, + "properties": { + "author": { + "type": "string" + }, + "keyboard": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "keyboard", + "name" + ], + "type": "object" + }, + "keystroke": { + "additionalProperties": false, + "properties": { + "flick": { + "type": "string" + }, + "key": { + "type": "string" + }, + "longPress": { + "type": "string" + }, + "tapCount": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "repertoire": { + "additionalProperties": false, + "properties": { + "chars": { + "type": "string" + }, + "name": { + "type": "string" + }, + "type": { + "enum": [ + "default", + "simple", + "gesture", + "flick", + "longPress", + "multiTap", + "hardware" + ], + "type": "string" + } + }, + "required": [ + "chars", + "name" + ], + "type": "object" + }, + "special": { + "$ref": "#/definitions/any" + }, + "startContext": { + "additionalProperties": false, + "properties": { + "to": { + "type": "string" + } + }, + "required": [ + "to" + ], + "type": "object" + }, + "test": { + "additionalProperties": false, + "properties": { + "backspace": { + "$ref": "#/definitions/backspace" + }, + "check": { + "$ref": "#/definitions/check" + }, + "emit": { + "$ref": "#/definitions/emit" + }, + "keystroke": { + "$ref": "#/definitions/keystroke" + }, + "name": { + "type": "string" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + }, + "startContext": { + "$ref": "#/definitions/startContext" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "tests": { + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + }, + "test": { + "items": { + "$ref": "#/definitions/test" + }, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "test", + "name" + ], + "type": "object" + } + }, + "properties": { + "keyboardTest": { + "additionalProperties": false, + "properties": { + "conformsTo": { + "enum": [ + "techpreview" + ], + "type": "string" + }, + "info": { + "$ref": "#/definitions/info" + }, + "repertoire": { + "items": { + "$ref": "#/definitions/repertoire" + }, + "type": "array" + }, + "special": { + "items": { + "$ref": "#/definitions/special" + }, + "type": "array" + }, + "tests": { + "items": { + "$ref": "#/definitions/tests" + }, + "type": "array" + } + }, + "required": [ + "info", + "conformsTo" + ] + } + }, + "required": [ + "keyboardTest" + ], + "title": "techpreview/dtd/ldmlKeyboardTest.xsd", + "type": "object" +} diff --git a/resources/standards-data/ldml-keyboards/techpreview/readme.md b/resources/standards-data/ldml-keyboards/techpreview/readme.md new file mode 100644 index 00000000000..b2265826b79 --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/readme.md @@ -0,0 +1,3 @@ +# ldml-keyboards/techpreview + +See [../readme.md](../readme.md) diff --git a/resources/standards-data/ldml-keyboards/techpreview/test/fr-t-k0-azerty-test.xml b/resources/standards-data/ldml-keyboards/techpreview/test/fr-t-k0-azerty-test.xml new file mode 100644 index 00000000000..638fe3954fd --- /dev/null +++ b/resources/standards-data/ldml-keyboards/techpreview/test/fr-t-k0-azerty-test.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/resources/stats/stats.sh b/resources/stats/stats.sh index cb9c02fbb42..0a23e38e9bf 100755 --- a/resources/stats/stats.sh +++ b/resources/stats/stats.sh @@ -5,8 +5,8 @@ set -u ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary -THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" -. "$(dirname "$THIS_SCRIPT")/../build/build-utils.sh" +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" diff --git a/tsconfig.json b/tsconfig.cjs.json similarity index 87% rename from tsconfig.json rename to tsconfig.cjs.json index 45baebc661e..1a342edea0d 100644 --- a/tsconfig.json +++ b/tsconfig.cjs.json @@ -1,4 +1,7 @@ { + // 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": [ @@ -8,23 +11,27 @@ { "path": "./common/web/recorder/src/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/browser.tsconfig.json" }, { "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": "./common/predictive-text/tsconfig.json" }, - { "path": "./developer/src/kmlmc/tests/tsconfig.json" }, - { "path": "./developer/src/kmlmc/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" }, { "path": "./web/source/tsconfig.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/web/keyman-version/" }, diff --git a/tsconfig.esm-base.json b/tsconfig.esm-base.json new file mode 100644 index 00000000000..a6f194d83ee --- /dev/null +++ b/tsconfig.esm-base.json @@ -0,0 +1,24 @@ +{ + "extends": "./tsconfig-base.json", + + "compilerOptions": { + "module": "ES2022", + "target": "es2022", + "moduleResolution": "Node", + "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.esm.json new file mode 100644 index 00000000000..636b3ff0c1f --- /dev/null +++ b/tsconfig.esm.json @@ -0,0 +1,25 @@ +{ + // 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": "./developer/src/kmc/tsconfig.json" }, + + { "path": "./developer/src/kmc-keyboard/test/tsconfig.json" }, + { "path": "./developer/src/kmc-keyboard/tsconfig.json" }, + { "path": "./developer/src/kmc-model/test/tsconfig.json" }, + { "path": "./developer/src/kmc-model/tsconfig.json" }, + //{ "path": "./developer/src/kmc-model-info/test/tsconfig.json" }, + { "path": "./developer/src/kmc-model-info/tsconfig.json" }, + { "path": "./developer/src/kmc-package/test/tsconfig.json" }, + { "path": "./developer/src/kmc-package/tsconfig.json" }, + + { "path": "./common/web/keyman-version/tsconfig.esm.json" }, + { "path": "./common/web/types/" }, + ] +} \ No newline at end of file diff --git a/windows/src/buildtools/buildpkg/buildpkg.dpr b/windows/src/buildtools/buildpkg/buildpkg.dpr index e1ebdb363ee..d2c7d21cfa7 100644 --- a/windows/src/buildtools/buildpkg/buildpkg.dpr +++ b/windows/src/buildtools/buildpkg/buildpkg.dpr @@ -31,7 +31,6 @@ uses utilhttp in '..\..\..\..\common\windows\delphi\general\utilhttp.pas', kmxfile in '..\..\..\..\common\windows\delphi\keyboards\kmxfile.pas', utilkeyboard in '..\..\..\..\common\windows\delphi\keyboards\utilkeyboard.pas', - CRC32 in '..\..\..\..\common\windows\delphi\general\CRC32.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', diff --git a/windows/src/buildtools/buildpkg/buildpkg.dproj b/windows/src/buildtools/buildpkg/buildpkg.dproj index 49fdf4ab5a9..16dbddf62fb 100644 --- a/windows/src/buildtools/buildpkg/buildpkg.dproj +++ b/windows/src/buildtools/buildpkg/buildpkg.dproj @@ -121,7 +121,6 @@ - diff --git a/windows/src/desktop/kmshell/kmshell.dpr b/windows/src/desktop/kmshell/kmshell.dpr index 6d4fa12bf4f..befb47fcf92 100644 --- a/windows/src/desktop/kmshell/kmshell.dpr +++ b/windows/src/desktop/kmshell/kmshell.dpr @@ -107,7 +107,6 @@ uses utilolepicture in '..\..\engine\kmcomapi\util\utilolepicture.pas', kmxfile in '..\..\..\..\common\windows\delphi\keyboards\kmxfile.pas', utilfiletypes in '..\..\..\..\common\windows\delphi\general\utilfiletypes.pas', - CRC32 in '..\..\..\..\common\windows\delphi\general\CRC32.pas', input_installlayoutortip in '..\..\global\delphi\winapi\input_installlayoutortip.pas', KPInstallPackageStartMenu in '..\..\global\delphi\general\KPInstallPackageStartMenu.pas', kmpinffile in '..\..\..\..\common\windows\delphi\packages\kmpinffile.pas', diff --git a/windows/src/desktop/kmshell/kmshell.dproj b/windows/src/desktop/kmshell/kmshell.dproj index 5be737bb8cb..5d716d8c4a5 100644 --- a/windows/src/desktop/kmshell/kmshell.dproj +++ b/windows/src/desktop/kmshell/kmshell.dproj @@ -262,7 +262,6 @@ - diff --git a/windows/src/engine/inst/insthelper/insthelper.dpr b/windows/src/engine/inst/insthelper/insthelper.dpr index cee805d7fe8..789319e4917 100644 --- a/windows/src/engine/inst/insthelper/insthelper.dpr +++ b/windows/src/engine/inst/insthelper/insthelper.dpr @@ -17,7 +17,6 @@ uses Classes, RegistryKeys in '..\..\..\..\..\common\windows\delphi\general\RegistryKeys.pas', klog in '..\..\..\..\..\common\windows\delphi\general\klog.pas', - CRC32 in '..\..\..\..\..\common\windows\delphi\general\CRC32.pas', VersionInfo in '..\..\..\..\..\common\windows\delphi\general\VersionInfo.pas', GetOsVersion in '..\..\..\..\..\common\windows\delphi\general\GetOsVersion.pas', KeymanVersion in '..\..\..\..\..\common\windows\delphi\general\KeymanVersion.pas', diff --git a/windows/src/engine/inst/insthelper/insthelper.dproj b/windows/src/engine/inst/insthelper/insthelper.dproj index d21c9a8e910..cffff989655 100644 --- a/windows/src/engine/inst/insthelper/insthelper.dproj +++ b/windows/src/engine/inst/insthelper/insthelper.dproj @@ -106,7 +106,6 @@ - diff --git a/windows/src/engine/keyman/keyman.dpr b/windows/src/engine/keyman/keyman.dpr index f425f89042e..9f8a3fba8e9 100644 --- a/windows/src/engine/keyman/keyman.dpr +++ b/windows/src/engine/keyman/keyman.dpr @@ -9,7 +9,6 @@ uses custinterfaces in '..\..\global\delphi\cust\custinterfaces.pas', Unicode in '..\..\..\..\common\windows\delphi\general\Unicode.pas', GetOsVersion in '..\..\..\..\common\windows\delphi\general\GetOsVersion.pas', - CRC32 in '..\..\..\..\common\windows\delphi\general\CRC32.pas', KeyNames in '..\..\..\..\common\windows\delphi\general\KeyNames.pas', MessageIdentifiers in '..\..\global\delphi\cust\MessageIdentifiers.pas', kmint in 'kmint.pas', diff --git a/windows/src/engine/keyman/keyman.dproj b/windows/src/engine/keyman/keyman.dproj index ac2ead4c578..44959854d5a 100644 --- a/windows/src/engine/keyman/keyman.dproj +++ b/windows/src/engine/keyman/keyman.dproj @@ -112,7 +112,6 @@ - diff --git a/windows/src/engine/keyman32/K32_load.cpp b/windows/src/engine/keyman32/K32_load.cpp index 9c2a9f8f5f9..190cf53e79e 100644 --- a/windows/src/engine/keyman32/K32_load.cpp +++ b/windows/src/engine/keyman32/K32_load.cpp @@ -31,7 +31,7 @@ #include "pch.h" HBITMAP LoadBitmapFile(LPBYTE data, DWORD sz); -BOOL VerifyKeyboard(LPBYTE filebase, DWORD sz); +BOOL VerifyKeyboard(LPBYTE filebase); #ifdef _WIN64 LPKEYBOARD CopyKeyboard(PBYTE bufp, PBYTE base, DWORD dwFileSize); @@ -286,7 +286,7 @@ BOOL LoadKeyboard(LPSTR fileName, LPKEYBOARD *lpKeyboard) return FALSE; } - if(!VerifyKeyboard(filebase, sz)) return FALSE; + if(!VerifyKeyboard(filebase)) return FALSE; #ifdef _WIN64 kbp = CopyKeyboard(buf, filebase, sz); @@ -892,20 +892,8 @@ HBITMAP LoadBitmapFileEx(PBYTE filebase) return NULL; } -BOOL VerifyChecksum(LPBYTE buf, DWORD sz) -{ - DWORD tempcs; - PCOMP_KEYBOARD ckbp; - - ckbp = (PCOMP_KEYBOARD) buf; - - tempcs = ckbp->dwCheckSum; - ckbp->dwCheckSum = 0; - - return tempcs == CalculateBufferCRC(sz, buf); -} -BOOL VerifyKeyboard(LPBYTE filebase, DWORD sz) +BOOL VerifyKeyboard(LPBYTE filebase) { DWORD i; PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) filebase; @@ -916,26 +904,21 @@ BOOL VerifyKeyboard(LPBYTE filebase, DWORD sz) if(ckbp->dwFileVersion < VERSION_MIN || ckbp->dwFileVersion > VERSION_MAX) { - /* Old or new version -- identify the desired program version */ - if(VerifyChecksum(filebase, sz)) - { - for(csp = (PCOMP_STORE)(filebase + ckbp->dpStoreArray), i = 0; i < ckbp->cxStoreArray; i++, csp++) - if(csp->dwSystemID == TSS_COMPILEDVERSION) - { - char buf2[256]; - if(csp->dpString == 0) - wsprintf(buf2, "errWrongFileVersion:NULL"); - else - wsprintf(buf2, "errWrongFileVersion:%10.10ls", StringOffset(filebase, csp->dpString)); - Err(buf2); - return FALSE; - } + for(csp = (PCOMP_STORE)(filebase + ckbp->dpStoreArray), i = 0; i < ckbp->cxStoreArray; i++, csp++) { + if(csp->dwSystemID == TSS_COMPILEDVERSION) + { + char buf2[256]; + if(csp->dpString == 0) + wsprintf(buf2, "errWrongFileVersion:NULL"); + else + wsprintf(buf2, "errWrongFileVersion:%10.10ls", StringOffset(filebase, csp->dpString)); + Err(buf2); + return FALSE; + } } Err("errWrongFileVersion"); return FALSE; } - if(!VerifyChecksum(filebase, sz)) { Err("errBadChecksum"); return FALSE; } - return TRUE; } diff --git a/windows/src/engine/keyman32/Makefile b/windows/src/engine/keyman32/Makefile index 325302e1ac9..11bb6c7c214 100644 --- a/windows/src/engine/keyman32/Makefile +++ b/windows/src/engine/keyman32/Makefile @@ -45,7 +45,7 @@ BUILD_DEBUG= pull-core: cd $(ROOT)\..\core - $(CALLER) ./build.sh -p native $(BUILD_DEBUG) + $(CALLER) ./build.sh --no-tests clean:x86 configure:x86 build:x86 clean:x64 configure:x64 build:x64 $(BUILD_DEBUG) cd $(ROOT)\src\engine\keyman32 !include ..\..\Target.mak diff --git a/windows/src/engine/keyman32/kmprocess.cpp b/windows/src/engine/keyman32/kmprocess.cpp index 35e30c68572..7faac081c27 100644 --- a/windows/src/engine/keyman32/kmprocess.cpp +++ b/windows/src/engine/keyman32/kmprocess.cpp @@ -112,7 +112,7 @@ Process_Event_Core(PKEYMAN64THREADDATA _td) { // Mask the bits supported according to `km_kbp_modifier_state` enum, update the mask if this enum is expanded. if (KM_KBP_STATUS_OK != km_kbp_process_event( _td->lpActiveKeyboard->lpCoreKeyboardState, _td->state.vkey, - static_cast(Globals::get_ShiftState() & (KM_KBP_MODIFIER_MASK_ALL | KM_KBP_MODIFIER_MASK_CAPS)), (uint8_t)_td->state.isDown)) { + static_cast(Globals::get_ShiftState() & (KM_KBP_MODIFIER_MASK_ALL | KM_KBP_MODIFIER_MASK_CAPS)), (uint8_t)_td->state.isDown, KM_KBP_EVENT_FLAG_DEFAULT)) { SendDebugMessageFormat(0, sdmGlobal, 0, "ProcessEvent CoreProcessEvent Result:False %d ", FALSE); return FALSE; } diff --git a/windows/src/engine/kmcomapi/kmcomapi.dpr b/windows/src/engine/kmcomapi/kmcomapi.dpr index 0db7f5ea069..d77fb3393eb 100644 --- a/windows/src/engine/kmcomapi/kmcomapi.dpr +++ b/windows/src/engine/kmcomapi/kmcomapi.dpr @@ -47,7 +47,6 @@ uses isadmin in 'util\isadmin.pas', baseerror in 'util\baseerror.pas', utilkeyman in 'util\utilkeyman.pas', - CRC32 in '..\..\..\..\common\windows\delphi\general\CRC32.pas', KeyNames in '..\..\..\..\common\windows\delphi\general\KeyNames.pas', RegistryKeys in '..\..\..\..\common\windows\delphi\general\RegistryKeys.pas', GetOsVersion in '..\..\..\..\common\windows\delphi\general\GetOsVersion.pas', diff --git a/windows/src/engine/kmcomapi/kmcomapi.dproj b/windows/src/engine/kmcomapi/kmcomapi.dproj index 9cf288d95c8..3f972b308da 100644 --- a/windows/src/engine/kmcomapi/kmcomapi.dproj +++ b/windows/src/engine/kmcomapi/kmcomapi.dproj @@ -185,7 +185,6 @@ - diff --git a/windows/src/engine/mcompile/mc_crc32.cpp b/windows/src/engine/mcompile/mc_crc32.cpp deleted file mode 100644 index 728deaf4c08..00000000000 --- a/windows/src/engine/mcompile/mc_crc32.cpp +++ /dev/null @@ -1,64 +0,0 @@ - -#include "pch.h" - -#define CRC32_POLYNOMIAL 0xEDB88320 - -unsigned long CRCTable[256]; -int TableBuilt = 0; - -void BuildCRCTable() -{ - int i, j; - unsigned long crc; - - if(!TableBuilt) - for(i = 0; i < 256; i++) - { - crc = i; - - for(j = 8; j >= 1; j--) - if((crc & 1)) crc = (crc >> 1) ^ CRC32_POLYNOMIAL; else crc >>= 1; - - CRCTable[i] = crc; - } - TableBuilt = 1; -} - -/* - * This routine calculates the CRC for a block of data using the - * table lookup method. It accepts an original value for the crc, - * and returns the updated value. - */ - -unsigned long CalculateBufferCRC(unsigned char *p, unsigned long count) -{ - unsigned long temp1, temp2, crc; - - crc = 0xFFFFFFFF; - - if(!TableBuilt) BuildCRCTable(); - - while(count > 0) - { - temp1 = (crc >> 8) & 0x00FFFFFF; - temp2 = CRCTable[((int)crc ^ (int)*p) & 0xFF]; - crc = temp1 ^ temp2; - p++; count--; - } - - return crc; -} - -unsigned long CalculateBufferCRC(unsigned long count, unsigned char *p) -{ - return CalculateBufferCRC(p, count); -} - -void Dehash(unsigned char *buf, unsigned long len) -{ - while(len > 0) - { - *buf = *buf ^ 0x6D; - buf++; len--; - } -} diff --git a/windows/src/engine/mcompile/mc_kmxfile.cpp b/windows/src/engine/mcompile/mc_kmxfile.cpp index 6f14a594e72..e5da6fe27ae 100644 --- a/windows/src/engine/mcompile/mc_kmxfile.cpp +++ b/windows/src/engine/mcompile/mc_kmxfile.cpp @@ -112,18 +112,6 @@ LPKEYBOARD FixupKeyboard(PBYTE bufp, PBYTE base, DWORD dwFileSize) { return kbp; } -BOOL VerifyChecksum(LPBYTE buf, DWORD sz) { - DWORD tempcs; - PCOMP_KEYBOARD ckbp; - - ckbp = (PCOMP_KEYBOARD) buf; - - tempcs = ckbp->dwCheckSum; - ckbp->dwCheckSum = 0; - - return tempcs == CalculateBufferCRC(sz, buf); -} - BOOL VerifyKeyboard(LPBYTE filebase, DWORD sz) { DWORD i; PCOMP_KEYBOARD ckbp = (PCOMP_KEYBOARD) filebase; @@ -134,7 +122,6 @@ BOOL VerifyKeyboard(LPBYTE filebase, DWORD sz) { if(ckbp->dwFileVersion < VERSION_MIN || ckbp->dwFileVersion > VERSION_MAX) { /* Old or new version -- identify the desired program version */ - if(VerifyChecksum(filebase, sz)) { for(csp = (PCOMP_STORE)(filebase + ckbp->dpStoreArray), i = 0; i < ckbp->cxStoreArray; i++, csp++) { if(csp->dwSystemID == TSS_COMPILEDVERSION) { wchar_t buf2[256]; @@ -146,16 +133,11 @@ BOOL VerifyKeyboard(LPBYTE filebase, DWORD sz) { Err(buf2); return FALSE; } - } } Err(L"errWrongFileVersion"); return FALSE; } - if(!VerifyChecksum(filebase, sz)) { - Err(L"errBadChecksum"); - return FALSE; - } return TRUE; } diff --git a/windows/src/engine/mcompile/mc_kmxfile.h b/windows/src/engine/mcompile/mc_kmxfile.h index d227f6b2a54..557ba968c73 100644 --- a/windows/src/engine/mcompile/mc_kmxfile.h +++ b/windows/src/engine/mcompile/mc_kmxfile.h @@ -6,23 +6,23 @@ typedef struct tagSTORE { DWORD dwSystemID; PWSTR dpName; - PWSTR dpString; + PWSTR dpString; } STORE, *LPSTORE; typedef struct tagKEY { WCHAR Key; DWORD Line; DWORD ShiftFlags; - PWSTR dpOutput; - PWSTR dpContext; + PWSTR dpOutput; + PWSTR dpContext; } KEY, *LPKEY; typedef struct tagGROUP { PWSTR dpName; LPKEY dpKeyArray; // [LPKEY] address of first item in key array - PWSTR dpMatch; - PWSTR dpNoMatch; + PWSTR dpMatch; + PWSTR dpNoMatch; DWORD cxKeyArray; // in array entries BOOL fUsingKeys; // group(xx) [using keys] <-- specified or not } GROUP, *LPGROUP; @@ -32,8 +32,8 @@ typedef struct tagKEYBOARD { DWORD dwIdentifier; // Keyman compiled keyboard id DWORD dwFileVersion; // Version of the file - Keyman 4.0 is 0x0400 - - DWORD dwCheckSum; // As stored in keyboard + + DWORD dwCheckSum; // As stored in keyboard. DEPRECATED as of 16.0 DWORD xxkbdlayout; // as stored in HKEY_LOCAL_MACHINE//system//currentcontrolset//control//keyboard layouts DWORD IsRegistered; // layout id, from same registry key DWORD version; // keyboard version @@ -43,7 +43,7 @@ typedef struct tagKEYBOARD { LPSTORE dpStoreArray; // [LPSTORE] address of first item in store array, from start of file LPGROUP dpGroupArray; // [LPGROUP] address of first item in group array, from start of file - + DWORD StartGroup[2]; // index of starting groups [2 of them] // Ansi=0, Unicode=1 @@ -63,4 +63,4 @@ typedef struct tagKEYBOARD { BOOL LoadKeyboard(LPWSTR fileName, LPKEYBOARD *lpKeyboard); -#endif \ No newline at end of file +#endif diff --git a/windows/src/engine/mcompile/mc_savekeyboard.cpp b/windows/src/engine/mcompile/mc_savekeyboard.cpp index 0a9c8b7c7f3..cbb13fee15f 100644 --- a/windows/src/engine/mcompile/mc_savekeyboard.cpp +++ b/windows/src/engine/mcompile/mc_savekeyboard.cpp @@ -33,12 +33,6 @@ BOOL SaveKeyboard(LPKEYBOARD kbd, PWSTR filename) { return TRUE; } -void SetChecksum(LPBYTE buf, LPDWORD CheckSum, DWORD sz) -{ - BuildCRCTable(); - *CheckSum = CalculateBufferCRC(buf, sz); -} - DWORD WriteCompiledKeyboard(LPKEYBOARD fk, HANDLE hOutfile, BOOL FSaveDebug) { LPGROUP fgp; @@ -93,7 +87,7 @@ DWORD WriteCompiledKeyboard(LPKEYBOARD fk, HANDLE hOutfile, BOOL FSaveDebug) ck->dwIdentifier = FILEID_COMPILED; ck->dwFileVersion = fk->dwFileVersion; - ck->dwCheckSum = 0; // do checksum afterwards. + ck->dwCheckSum = 0; // No checksum in 16.0, see #7276 ck->KeyboardID = fk->xxkbdlayout; ck->IsRegistered = fk->IsRegistered; ck->cxStoreArray = fk->cxStoreArray; @@ -207,8 +201,6 @@ DWORD WriteCompiledKeyboard(LPKEYBOARD fk, HANDLE hOutfile, BOOL FSaveDebug) return CERR_SomewhereIGotItWrong; } - SetChecksum(buf, &ck->dwCheckSum, size); - WriteFile(hOutfile, buf, size, &offset, NULL); if(offset != size) diff --git a/windows/src/engine/mcompile/mcompile.vcxproj b/windows/src/engine/mcompile/mcompile.vcxproj index d68b08883d8..e9c426a330d 100644 --- a/windows/src/engine/mcompile/mcompile.vcxproj +++ b/windows/src/engine/mcompile/mcompile.vcxproj @@ -110,7 +110,6 @@ - diff --git a/windows/src/engine/mcompile/mcompile.vcxproj.filters b/windows/src/engine/mcompile/mcompile.vcxproj.filters index a29fc0e01df..360aee2a109 100644 --- a/windows/src/engine/mcompile/mcompile.vcxproj.filters +++ b/windows/src/engine/mcompile/mcompile.vcxproj.filters @@ -44,9 +44,6 @@ Library Source Files - - Source Files - Source Files diff --git a/windows/src/engine/tsysinfo/tsysinfo.dpr b/windows/src/engine/tsysinfo/tsysinfo.dpr index d333e75d97b..8683352a3b5 100644 --- a/windows/src/engine/tsysinfo/tsysinfo.dpr +++ b/windows/src/engine/tsysinfo/tsysinfo.dpr @@ -19,7 +19,6 @@ uses kmxfile in '..\..\..\..\common\windows\delphi\keyboards\kmxfile.pas', utilkeyboard in '..\..\..\..\common\windows\delphi\keyboards\utilkeyboard.pas', utilfiletypes in '..\..\..\..\common\windows\delphi\general\utilfiletypes.pas', - CRC32 in '..\..\..\..\common\windows\delphi\general\CRC32.pas', KeyNames in '..\..\..\..\common\windows\delphi\general\KeyNames.pas', ttinfo in '..\..\..\..\common\windows\delphi\general\ttinfo.pas', Upload_Settings in '..\..\..\..\common\windows\delphi\general\Upload_Settings.pas', diff --git a/windows/src/engine/tsysinfo/tsysinfo.dproj b/windows/src/engine/tsysinfo/tsysinfo.dproj index eaca7762c25..c738af46932 100644 --- a/windows/src/engine/tsysinfo/tsysinfo.dproj +++ b/windows/src/engine/tsysinfo/tsysinfo.dproj @@ -166,7 +166,6 @@ - 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 index 6f4a235b9a1..975eda527d0 100644 --- a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dpr +++ b/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dpr @@ -14,7 +14,6 @@ uses 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', - CRC32 in 'C:\Projects\keyman\open\common\windows\delphi\general\CRC32.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', 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 index 48edcb38d9b..2c70ec7ca61 100644 --- a/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dproj +++ b/windows/src/support/convert-iso6393-to-bcp47-for-keyboards/cvt.dproj @@ -105,7 +105,6 @@ - diff --git a/windows/src/support/create_normalization_group/normgrp.dpr b/windows/src/support/create_normalization_group/normgrp.dpr index 8d1c247d6c4..a48480e27c0 100644 --- a/windows/src/support/create_normalization_group/normgrp.dpr +++ b/windows/src/support/create_normalization_group/normgrp.dpr @@ -9,7 +9,6 @@ uses kmxfileconsts in '..\..\..\..\common\windows\delphi\keyboards\kmxfileconsts.pas', kmxfileusedchars in '..\..\global\delphi\general\kmxfileusedchars.pas', kmxfileutils in '..\..\..\..\common\windows\delphi\keyboards\kmxfileutils.pas', - CRC32 in '..\..\..\..\common\windows\delphi\general\CRC32.pas', KeyNames in '..\..\..\..\common\windows\delphi\general\KeyNames.pas', utildir in '..\..\..\..\common\windows\delphi\general\utildir.pas', utilfiletypes in '..\..\..\..\common\windows\delphi\general\utilfiletypes.pas', diff --git a/windows/src/support/kmxfonts/kmxfonts.dpr b/windows/src/support/kmxfonts/kmxfonts.dpr index 86a9bf1ce94..09ffa1ef95e 100644 --- a/windows/src/support/kmxfonts/kmxfonts.dpr +++ b/windows/src/support/kmxfonts/kmxfonts.dpr @@ -9,7 +9,6 @@ uses kmxfileconsts in '..\..\..\..\common\windows\delphi\keyboards\kmxfileconsts.pas', kmxfileutils in '..\..\..\..\common\windows\delphi\keyboards\kmxfileutils.pas', Unicode in '..\..\..\..\common\windows\delphi\general\Unicode.pas', - CRC32 in '..\..\..\..\common\windows\delphi\general\CRC32.pas', KeyNames in '..\..\..\..\common\windows\delphi\general\KeyNames.pas', utildir in '..\..\..\..\common\windows\delphi\general\utildir.pas', utilfiletypes in '..\..\..\..\common\windows\delphi\general\utilfiletypes.pas', diff --git a/windows/src/support/kmxfonts/kmxfonts.dproj b/windows/src/support/kmxfonts/kmxfonts.dproj index 1964ec9750f..48e53af8e87 100644 --- a/windows/src/support/kmxfonts/kmxfonts.dproj +++ b/windows/src/support/kmxfonts/kmxfonts.dproj @@ -225,7 +225,6 @@ - diff --git a/windows/src/test/manual-tests/test_iconsave/test_iconsave.dpr b/windows/src/test/manual-tests/test_iconsave/test_iconsave.dpr index cf8e3931ad5..f6051d19e4f 100644 --- a/windows/src/test/manual-tests/test_iconsave/test_iconsave.dpr +++ b/windows/src/test/manual-tests/test_iconsave/test_iconsave.dpr @@ -5,7 +5,6 @@ uses test_iconsave_form in 'test_iconsave_form.pas' {Form1}, utilicon in '..\..\..\..\..\common\windows\delphi\general\utilicon.pas', kmxfile in '..\..\..\..\..\common\windows\delphi\keyboards\kmxfile.pas', - CRC32 in '..\..\..\..\..\common\windows\delphi\general\CRC32.pas', crypt_base in '..\..\global\delphi\productactivation\crypt_base.pas', crypt_guid in '..\..\global\delphi\productactivation\crypt_guid.pas', productactivation in '..\..\global\delphi\productactivation\productactivation.pas', diff --git a/windows/src/test/manual-tests/test_iconsave/test_iconsave.dproj b/windows/src/test/manual-tests/test_iconsave/test_iconsave.dproj index 0505a260075..416ce552821 100644 --- a/windows/src/test/manual-tests/test_iconsave/test_iconsave.dproj +++ b/windows/src/test/manual-tests/test_iconsave/test_iconsave.dproj @@ -91,7 +91,6 @@ -