mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-27 10:07:40 +00:00
chore(web): Merge branch 'master' into maint/web/13399_tc-config_refactor
This commit is contained in:
commit
ab2f7c2d30
84 changed files with 1272 additions and 647 deletions
|
|
@ -31,7 +31,7 @@ runs:
|
|||
path: artifacts/keyman-srcpkg
|
||||
|
||||
- name: Build
|
||||
uses: sillsdev/gha-ubuntu-packaging@b619077451b27c16dc6fd699bc1daf8d5ce07659 # v1.2
|
||||
uses: sillsdev/gha-ubuntu-packaging@556b268762be9bea0f39861a7392587211ef6a58 # v2.0.1
|
||||
with:
|
||||
dist: "${{ inputs.dist }}"
|
||||
platform: "${{ inputs.arch }}"
|
||||
|
|
|
|||
47
.github/labeler.yml
vendored
47
.github/labeler.yml
vendored
|
|
@ -11,43 +11,56 @@ docs: docs/**
|
|||
# Add labels based on changed files using actions/labeler
|
||||
#
|
||||
|
||||
android/: android/**
|
||||
android/:
|
||||
- android/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/android/**
|
||||
android/app/: android/KMAPro/**
|
||||
android/engine/: android/KMEA/**
|
||||
android/samples/: android/Samples/**
|
||||
|
||||
common/: common/**
|
||||
common/:
|
||||
- common/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/common/**
|
||||
common/web/: common/web/**
|
||||
|
||||
core/:
|
||||
- core/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/core/**
|
||||
|
||||
developer/:
|
||||
- developer/**
|
||||
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/developer/**
|
||||
developer/compilers/:
|
||||
- developer/src/kmc/**
|
||||
- developer/src/kmcmplib/**
|
||||
- developer/src/kmc-*/**
|
||||
|
||||
developer/ide/:
|
||||
- developer/src/server/**
|
||||
- developer/src/tike/**
|
||||
|
||||
ios/: ios/**
|
||||
ios/:
|
||||
- ios/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/ios/**
|
||||
ios/app/: ios/keyman/**
|
||||
ios/engine/: ios/engine/**
|
||||
ios/samples/: ios/samples/**
|
||||
|
||||
linux/: linux/**
|
||||
linux/:
|
||||
- linux/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/linux/**
|
||||
linux/config/: linux/keyman-config/**
|
||||
linux/engine/:
|
||||
- linux/ibus-keyman/**
|
||||
- linux/legacy/ibus-kmfl/**
|
||||
- linux/legacy/kmflcomp/**
|
||||
- linux/legacy/libkmfl/**
|
||||
linux/engine/: linux/ibus-keyman/**
|
||||
|
||||
mac/: mac/**
|
||||
mac/:
|
||||
- mac/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/mac/**
|
||||
# mac/config/:
|
||||
# mac/engine/: mac/**
|
||||
|
||||
|
|
@ -59,13 +72,19 @@ oem/fv/windows/: oem/firstvoices/windows/**
|
|||
|
||||
resources/: resources/**
|
||||
|
||||
web/: web/**
|
||||
web/:
|
||||
- web/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/web/**
|
||||
# web/bookmarklet/
|
||||
web/engine/: web/source/**
|
||||
web/ui/: web/source/kmwui*
|
||||
web/samples/: web/samples/**
|
||||
web/predictive-text/: web/src/engine/predictive-text/**
|
||||
|
||||
windows/: windows/**
|
||||
windows/:
|
||||
- windows/**
|
||||
- resources/teamcity/includes/**
|
||||
- resources/teamcity/windows/**
|
||||
windows/config/: windows/src/desktop/**
|
||||
windows/engine/: windows/src/engine/**
|
||||
|
|
|
|||
69
HISTORY.md
69
HISTORY.md
|
|
@ -1,5 +1,48 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.57 alpha 2025-06-03
|
||||
|
||||
* fix(windows): change from error to breadcrumb and log (#14069)
|
||||
* maint(mac): make make-km-dmg.sh less quiet (#14105)
|
||||
|
||||
## 19.0.56 alpha 2025-06-02
|
||||
|
||||
* maint(ios): Use zip.inc.sh to prep release artifacts (#13919)
|
||||
* maint(linux): update to new version of gha-ubuntu-packaging (#14094)
|
||||
* chore(linux): add Python TC test runner (#14062)
|
||||
|
||||
## 19.0.55 alpha 2025-05-31
|
||||
|
||||
* maint(linux): `sudo` not required for removing temporary files (#14086)
|
||||
* maint(linux): update to new version of gha-ubuntu-packaging (#14089)
|
||||
|
||||
## 19.0.54 alpha 2025-05-30
|
||||
|
||||
* maint(linux): move TC build scripts to `resources/teamcity/linux` (#14061)
|
||||
|
||||
## 19.0.53 alpha 2025-05-29
|
||||
|
||||
* fix(developer): ensure that kmc doesn't skip validate phase of kmc-ldml (#14068)
|
||||
|
||||
## 19.0.52 alpha 2025-05-28
|
||||
|
||||
* feat(developer): line numbers for tran compiler (#14037)
|
||||
* feat(developer): add line numbers to disp compiler (#14048)
|
||||
* feat(developer): line numbers for layr compiler (#14050)
|
||||
* feat(developer): line numbers for LocaCompiler (#14054)
|
||||
* fix(common): properly use newly installed node version (#14057)
|
||||
* maint(common): update build coordinator and labels for `resources/teamcity` changes (#14047)
|
||||
|
||||
## 19.0.51 alpha 2025-05-27
|
||||
|
||||
* maint: add stable-19.0 triggers (#14043)
|
||||
* maint: add empty 'scripts' tag to package.json to silence warning (#14044)
|
||||
* fix(linux): fix version variable references in kmconfig (#14032)
|
||||
|
||||
## 19.0.50 alpha 2025-05-26
|
||||
|
||||
* chore(web): mark async methods in UI components (#14034)
|
||||
|
||||
## 19.0.49 alpha 2025-05-25
|
||||
|
||||
* feat(developer): revamp line numbers per design doc (#14016)
|
||||
|
|
@ -302,6 +345,32 @@
|
|||
* refactor(windows): rename `TKeymanMutex.MutexOwned` to `TakeOwnership` and add `ReleaseOwnership` (#13168)
|
||||
* chore: increment to alpha 19.0 (#13187)
|
||||
|
||||
## 18.0.238 stable 2025-06-04
|
||||
|
||||
* fix(developer): ldml: make sure .run() calls compile() for validation (#14071)
|
||||
* maint(linux): fix typo of temporary dependency package (#14085)
|
||||
* maint(linux): update to new version of gha-ubuntu-packaging (#14096)
|
||||
* maint(mac): make make-km-dmg.sh less quiet (#14106)
|
||||
|
||||
## 18.0.237 stable 2025-05-30
|
||||
|
||||
* maint(android): Update FirstVoices launcher icon and red theme (#13917)
|
||||
* fix(web): empty layout fontSize property should default to 1em (#13960)
|
||||
* fix(linux): use correct header guard define in OrderedOutputDevice.h (#13963)
|
||||
* chore(linux): Update debian changelog (#13888)
|
||||
* fix(mac): handle PackageInfo section in kmp.inf file (#13957)
|
||||
* feat(android): log active keyboard, model IDs with reported errors (#14003)
|
||||
* chore(ios): FirstVoices certificate update (#14005)
|
||||
* feat(ios): log active keyboard, model IDs with reported errors (#14006)
|
||||
* fix(linux): configurable path in `.service` file when using basu (#13999)
|
||||
* maint(linux): add `--no-werror` option to not report warnings as errors (#14000)
|
||||
* fix(windows): use json file name against cache folder and ename Enter and Exit methods for TState Object (#14010)
|
||||
* chore(ios): update FirstVoices AppIcon (#14011)
|
||||
* maint: change builder_completion script to a .inc.sh (#14008)
|
||||
* fix(windows): only breadcrumb unexpected state on first run (#14026)
|
||||
* fix(windows): skip sending handle kmshell events to upgrade state machine for keyboard install modes (#14028)
|
||||
* fix(windows): fix sentry error message reporting (#14029)
|
||||
|
||||
## 18.0.236 stable 2025-05-12
|
||||
|
||||
* chore(linux): Update debian changelog (#13737)
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.50
|
||||
19.0.58
|
||||
3
android/.gitignore
vendored
3
android/.gitignore
vendored
|
|
@ -57,6 +57,9 @@ KMAPro/**/assets/*.kmp
|
|||
# Play Store release notes (copied from whatsnew.md)
|
||||
KMAPro/kMAPro/src/main/play/release-notes/**/*.txt
|
||||
|
||||
# Release artifacts
|
||||
upload/
|
||||
|
||||
# Eclipse files
|
||||
.project
|
||||
.settings/
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
|
||||
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/zip.inc.sh"
|
||||
|
||||
################################ Main script ################################
|
||||
|
||||
|
|
@ -37,6 +38,7 @@ builder_describe \
|
|||
build \
|
||||
test \
|
||||
"publish Publishes symbols to Sentry and the APKs to the Play Store." \
|
||||
"archive Copy release artifacts to upload/ and rsync to downloads.keyman" \
|
||||
"--ci+ Deprecated build option. Remove in 20.0" \
|
||||
--upload-sentry+ \
|
||||
":engine=KMEA Keyman Engine for Android" \
|
||||
|
|
@ -70,4 +72,51 @@ function do_test_help() {
|
|||
|
||||
builder_run_action test:help do_test_help
|
||||
|
||||
builder_run_child_actions publish
|
||||
builder_run_child_actions publish
|
||||
|
||||
# Copy release artifacts to upload/ and rsync to downloads.keyman.com
|
||||
if builder_start_action archive; then
|
||||
UPLOAD_PATH="$KEYMAN_ROOT/android/upload/${KEYMAN_VERSION}"
|
||||
KEYMAN_ENGINE_ANDROID_ZIP="keyman-engine-android-${KEYMAN_VERSION}.zip"
|
||||
KEYMAN_APK="keyman-${KEYMAN_VERSION}.apk"
|
||||
FIRSTVOICES_APK="firstvoices-${KEYMAN_VERSION}.apk"
|
||||
ZIP_FILE="${UPLOAD_PATH}/${KEYMAN_ENGINE_ANDROID_ZIP}"
|
||||
ZIP_FLAGS=("-q" "-r") # quiet, recursive
|
||||
|
||||
mkdir -p "${UPLOAD_PATH}"
|
||||
|
||||
# Create Keyman Engine for Android archive
|
||||
builder_echo "Copying Keyman Engine for Android into ${UPLOAD_PATH}..."
|
||||
cd "${UPLOAD_PATH}"
|
||||
cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/libs/keyman-engine.aar" ./
|
||||
add_zip_files "${ZIP_FILE}" "${ZIP_FLAGS[@]}" "keyman-engine.aar"
|
||||
rm -f "keyman-engine.aar"
|
||||
|
||||
builder_echo "Copying Keyman Engine for Android Sample projects into ${UPLOAD_PATH}..."
|
||||
cp -rf "${KEYMAN_ROOT}/android/Samples" ./
|
||||
add_zip_files "${ZIP_FILE}" "-x@../../zip-excludes" "${ZIP_FLAGS[@]}" "Samples"
|
||||
rm -rf "Samples"
|
||||
|
||||
# Copy release APK
|
||||
cp "${KEYMAN_ROOT}/android/KMAPro/kMAPro/build/outputs/apk/release/${KEYMAN_APK}" ./
|
||||
|
||||
# FirstVoices app
|
||||
|
||||
if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then
|
||||
cp "${KEYMAN_ROOT}/oem/firstvoices/android/app/build/outputs/apk/release/${FIRSTVOICES_APK}" ./
|
||||
fi
|
||||
|
||||
#
|
||||
# Write download info files
|
||||
#
|
||||
|
||||
cd "${UPLOAD_PATH}"
|
||||
write_download_info "Keyman Engine for Android" "${KEYMAN_ENGINE_ANDROID_ZIP}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
|
||||
write_download_info "Keyman for Android" "${KEYMAN_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
|
||||
|
||||
if [ "${RELEASE_OEM_FIRSTVOICES-false}" = true ]; then
|
||||
write_download_info "FirstVoices Keyboards" "${FIRSTVOICES_APK}" "${KEYMAN_VERSION}" "${KEYMAN_TIER}" "android"
|
||||
fi
|
||||
|
||||
builder_finish_action success archive
|
||||
fi
|
||||
|
|
|
|||
1
android/zip-excludes
Normal file
1
android/zip-excludes
Normal file
|
|
@ -0,0 +1 @@
|
|||
Samples/*/build.sh
|
||||
|
|
@ -3,6 +3,7 @@
|
|||
"description": "Common test resources for Keyman's JS & TS-based components & products",
|
||||
"type": "module",
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"devDependencies": {
|
||||
"@keymanapp/resources-gosh": "*",
|
||||
"typescript": "^5.4.5"
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"/build/"
|
||||
],
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.5"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
import { DependencySections, StrsItem, UsetItem } from './kmx-plus.js';
|
||||
import { DependencySections, StrsItem, StrsOptions, UsetItem } from './kmx-plus.js';
|
||||
import { ElementParser, ElementSegment, ElementType } from '../../ldml-keyboard/pattern-parser.js';
|
||||
import * as util from '../../util/util.js';
|
||||
import MATCH_HEX_ESCAPE = util.MATCH_HEX_ESCAPE;
|
||||
|
|
@ -33,7 +33,7 @@ export class ElementString extends Array<ElemElement> {
|
|||
* @param source if a string array, does not get reinterpreted as UnicodeSet. This is used with vars, etc. Or pass `["str"]` for an explicit 1-element elem.
|
||||
* If it is a string, will be interpreted per reorder element rules.
|
||||
*/
|
||||
static fromStrings(sections: DependencySections, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) : ElementString {
|
||||
static fromStrings(sections: DependencySections, options: StrsOptions, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string) : ElementString {
|
||||
// the returned array
|
||||
const array = new ElementString();
|
||||
if(!source) {
|
||||
|
|
@ -81,21 +81,21 @@ export class ElementString extends Array<ElemElement> {
|
|||
// error. So we can just exit here.
|
||||
return null; // UnicodeSet error
|
||||
}
|
||||
const uset = sections.usetparser.parseUnicodeSet(item.segment, needRanges);
|
||||
const uset = sections.usetparser.parseUnicodeSet(item.segment, needRanges, options?.x);
|
||||
if (!uset) {
|
||||
return null; // UnicodeSet error already thrown
|
||||
return null; // UnicodeSet error already added to callback
|
||||
}
|
||||
elem.uset = sections.uset.allocUset(uset, sections);
|
||||
elem.value = sections.strs.allocString('', {singleOk: true}); // no string
|
||||
elem.uset = sections.uset.allocUset(uset, sections, options?.x);
|
||||
elem.value = sections.strs.allocString('', {...options, singleOk: true}); // no string
|
||||
} else if (item.type === ElementType.codepoint || item.type === ElementType.escaped || item.type === ElementType.string) {
|
||||
// some kind of a string
|
||||
let str = item.segment;
|
||||
if (item.type === ElementType.escaped && !MATCH_HEX_ESCAPE.test(str)) {
|
||||
str = unescapeOneQuadString(str);
|
||||
// TODO-LDML: any other escape forms here?
|
||||
elem.value = sections.strs.allocString(str, { singleOk: true });
|
||||
elem.value = sections.strs.allocString(str, { ...options, singleOk: true });
|
||||
} else {
|
||||
elem.value = sections.strs.allocString(str, { unescape: true, singleOk: true });
|
||||
elem.value = sections.strs.allocString(str, { ...options, unescape: true, singleOk: true });
|
||||
}
|
||||
// Now did we end up with one char or no?
|
||||
if (elem.value.isOneChar) {
|
||||
|
|
|
|||
|
|
@ -41,14 +41,14 @@ export class Elem extends Section {
|
|||
strings: ElementString[] = [];
|
||||
constructor(sections: DependencySections) {
|
||||
super();
|
||||
this.strings.push(ElementString.fromStrings(sections, '')); // C7043: null element string
|
||||
this.strings.push(ElementString.fromStrings(sections, {}, '')); // C7043: null element string
|
||||
}
|
||||
/**
|
||||
* @param source if a string array, does not get reinterpreted as UnicodeSet. This is used with vars, etc. Or pass `["str"]` for an explicit 1-element elem.
|
||||
* If it is a string, will be interpreted per reorder element ruls.
|
||||
*/
|
||||
allocElementString(sections: DependencySections, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string): ElementString {
|
||||
const s = ElementString.fromStrings(sections, source, order, tertiary, tertiary_base, prebase);
|
||||
allocElementString(sections: DependencySections, options: StrsOptions, source: string | string[], order?: string, tertiary?: string, tertiary_base?: string, prebase?: string): ElementString {
|
||||
const s = ElementString.fromStrings(sections, options, source, order, tertiary, tertiary_base, prebase);
|
||||
if (!s) return s;
|
||||
let result = this.strings.find(item => item.isEqual(s));
|
||||
if(result === undefined) {
|
||||
|
|
@ -135,6 +135,18 @@ export class StrsItem {
|
|||
isEqual(a: StrsItem): boolean {
|
||||
return a.value === this.value && a.char === this.char;
|
||||
}
|
||||
|
||||
private _context: any = undefined;
|
||||
|
||||
/** add any context from the options to this strsitem */
|
||||
setContext(opts?: StrsOptions) {
|
||||
// At present, there's only a single piece of context available
|
||||
this._context = this._context || opts?.x;
|
||||
}
|
||||
|
||||
get context() : any {
|
||||
return this._context;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -161,6 +173,8 @@ export interface StrsOptions {
|
|||
nfd?: boolean;
|
||||
/** string can be stored as a single CharStrsItem, not in strs table. */
|
||||
singleOk?: boolean;
|
||||
/** optional context */
|
||||
x?: any;
|
||||
};
|
||||
|
||||
export class Strs extends Section {
|
||||
|
|
@ -192,6 +206,8 @@ export class Strs extends Section {
|
|||
result = new StrsItem(s);
|
||||
this.strings.push(result);
|
||||
}
|
||||
// give an option to set the context
|
||||
result.setContext(opts);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
|
@ -200,7 +216,7 @@ export class Strs extends Section {
|
|||
s = s ?? '';
|
||||
// type check everything else
|
||||
if (typeof s !== 'string') {
|
||||
throw new Error('alloc_string: s must be a string, undefined, or null.');
|
||||
throw new Error(`Internal Error: processString: s must be a string, undefined, or null, not ${typeof s} ${s}`);
|
||||
}
|
||||
// substitute variables
|
||||
if (opts?.stringVariables) {
|
||||
|
|
@ -358,11 +374,13 @@ export class Vars extends Section {
|
|||
export class VarsItem extends Section {
|
||||
id: StrsItem;
|
||||
value: StrsItem;
|
||||
x?: any;
|
||||
|
||||
constructor(id: string, value: string, sections: DependencySections) {
|
||||
constructor(id: string, value: string, sections: DependencySections, x?: any) {
|
||||
super();
|
||||
this.id = sections.strs.allocString(id);
|
||||
this.value = sections.strs.allocString(value, {unescape: true});
|
||||
this.x = x;
|
||||
}
|
||||
|
||||
valid() : boolean {
|
||||
|
|
@ -371,8 +389,8 @@ export class VarsItem extends Section {
|
|||
};
|
||||
|
||||
export class UnicodeSetItem extends VarsItem {
|
||||
constructor(id: string, value: string, sections: DependencySections, usetparser: UnicodeSetParser) {
|
||||
super(id, value, sections);
|
||||
constructor(id: string, value: string, sections: DependencySections, usetparser: UnicodeSetParser, x?: any) {
|
||||
super(id, value, sections, x);
|
||||
const needRanges = sections.usetparser.sizeUnicodeSet(value);
|
||||
if (needRanges >= 0) {
|
||||
this.unicodeSet = sections.usetparser.parseUnicodeSet(value, needRanges);
|
||||
|
|
@ -385,9 +403,9 @@ export class UnicodeSetItem extends VarsItem {
|
|||
};
|
||||
|
||||
export class SetVarItem extends VarsItem {
|
||||
constructor(id: string, value: string[], sections: DependencySections) {
|
||||
super(id, value.join(' '), sections);
|
||||
this.items = sections.elem.allocElementString(sections, value);
|
||||
constructor(id: string, value: string[], sections: DependencySections, x?: any) {
|
||||
super(id, value.join(' '), sections, x);
|
||||
this.items = sections.elem.allocElementString(sections, {x}, value);
|
||||
}
|
||||
items: ElementString; // element string array
|
||||
valid() : boolean {
|
||||
|
|
@ -396,8 +414,8 @@ export class SetVarItem extends VarsItem {
|
|||
};
|
||||
|
||||
export class StringVarItem extends VarsItem {
|
||||
constructor(id: string, value: string, sections: DependencySections) {
|
||||
super(id, value, sections);
|
||||
constructor(id: string, value: string, sections: DependencySections, x?: any) {
|
||||
super(id, value, sections, x);
|
||||
}
|
||||
// no added fields
|
||||
};
|
||||
|
|
@ -435,7 +453,7 @@ export class Tran extends Section {
|
|||
};
|
||||
|
||||
export class UsetItem {
|
||||
constructor(public uset: UnicodeSet, public str: StrsItem) {
|
||||
constructor(public uset: UnicodeSet, public str: StrsItem, public x?: any) {
|
||||
}
|
||||
compareTo(other: UsetItem) : number {
|
||||
return this.str.compareTo(other.str);
|
||||
|
|
@ -444,11 +462,11 @@ export class UsetItem {
|
|||
|
||||
export class Uset extends Section {
|
||||
usets: UsetItem[] = [];
|
||||
allocUset(set: UnicodeSet, sections: DependencySections) : UsetItem {
|
||||
allocUset(set: UnicodeSet, sections: DependencySections, x?: any) : UsetItem {
|
||||
// match the same pattern
|
||||
let result = this.usets.find(s => set.pattern == s.uset.pattern);
|
||||
if (result === undefined) {
|
||||
result = new UsetItem(set, sections.strs.allocString(set.pattern));
|
||||
result = new UsetItem(set, sections.strs.allocString(set.pattern), x);
|
||||
this.usets.push(result);
|
||||
}
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -8,13 +8,13 @@ export interface UnicodeSetParser {
|
|||
* @param pattern string to parse such as `[a-z]`
|
||||
* @param rangeCount number of ranges to allow for
|
||||
*/
|
||||
parseUnicodeSet(pattern: string, rangeCount: number) : UnicodeSet | null;
|
||||
parseUnicodeSet(pattern: string, rangeCount: number, x?: any) : UnicodeSet | null;
|
||||
/**
|
||||
* Calculate the number of ranges in a UnicodeSet
|
||||
* @param pattern string to parse such as `[a-z]`
|
||||
* @returns number of ranges, or -1 (with callback-reported err) on err
|
||||
*/
|
||||
sizeUnicodeSet(pattern: string) : number;
|
||||
sizeUnicodeSet(pattern: string, x?: any) : number;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -91,12 +91,12 @@ describe('Test of ElementString file', () => {
|
|||
});
|
||||
describe('Test of fromStrings()', () => {
|
||||
it('returns an empty ElementString if source is null', () => {
|
||||
const es = ElementString.fromStrings({}, null);
|
||||
const es = ElementString.fromStrings({}, {}, null);
|
||||
assert.deepEqual(es, new ElementString());
|
||||
});
|
||||
it('can create an ElementString from a string array', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(sections, ["𐌰", "𐌱", "𐌲"]);
|
||||
const actual = ElementString.fromStrings(sections, {}, ["𐌰", "𐌱", "𐌲"]);
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
|
|
@ -106,7 +106,7 @@ describe('Test of ElementString file', () => {
|
|||
});
|
||||
it('can create an ElementString from a string', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(sections, "𐌰𐌱𐌲");
|
||||
const actual = ElementString.fromStrings(sections, {}, "𐌰𐌱𐌲");
|
||||
const expected = [
|
||||
initElemElement(GOTHIC_A),
|
||||
initElemElement(GOTHIC_B),
|
||||
|
|
@ -118,6 +118,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
"1 2 3",
|
||||
);
|
||||
|
|
@ -132,6 +133,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
"1",
|
||||
);
|
||||
|
|
@ -146,6 +148,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
"1 2",
|
||||
);
|
||||
|
|
@ -160,6 +163,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
"1 A 3",
|
||||
);
|
||||
|
|
@ -174,6 +178,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 2 3",
|
||||
|
|
@ -189,6 +194,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1",
|
||||
|
|
@ -204,6 +210,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 2",
|
||||
|
|
@ -219,6 +226,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
"1 A 3",
|
||||
|
|
@ -234,6 +242,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
|
|
@ -250,6 +259,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
|
|
@ -266,6 +276,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
|
|
@ -282,6 +293,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
|
|
@ -299,6 +311,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
|
|
@ -316,6 +329,7 @@ describe('Test of ElementString file', () => {
|
|||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
const actual = ElementString.fromStrings(
|
||||
sections,
|
||||
{},
|
||||
"𐌰𐌱𐌲",
|
||||
null,
|
||||
null,
|
||||
|
|
@ -333,7 +347,7 @@ describe('Test of ElementString file', () => {
|
|||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.strs.allocString = stubStrsAllocString_Str;
|
||||
sections.uset.allocUset = stubUsetAllocUset;
|
||||
const actual = ElementString.fromStrings(sections, "[𐌰-𐍊]");
|
||||
const actual = ElementString.fromStrings(sections, {}, "[𐌰-𐍊]");
|
||||
const expected = [
|
||||
initElemElement(
|
||||
new StrsItem(''),
|
||||
|
|
@ -348,17 +362,17 @@ describe('Test of ElementString file', () => {
|
|||
it('returns null for an invalid unicode set size', () => {
|
||||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.usetparser.sizeUnicodeSet = (pattern: string) : number => -1;
|
||||
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
|
||||
assert.isNull(ElementString.fromStrings(sections, {}, "[𐌰-𐍊]"));
|
||||
});
|
||||
it('returns null if it cannot parse the unicode set', () => {
|
||||
ElementParser.segment = stubElementParserSegment_Uset;
|
||||
sections.usetparser.parseUnicodeSet = (pattern: string, rangeCount: number) : UnicodeSet | null => null;
|
||||
assert.isNull(ElementString.fromStrings(sections, "[𐌰-𐍊]"));
|
||||
assert.isNull(ElementString.fromStrings(sections, {}, "[𐌰-𐍊]"));
|
||||
});
|
||||
it('can handle quad strings', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Char;
|
||||
ElementParser.segment = stubElementParserSegment_Escaped;
|
||||
const actual = ElementString.fromStrings(sections, "\\ud800\\udf30");
|
||||
const actual = ElementString.fromStrings(sections, {}, "\\ud800\\udf30");
|
||||
const expected = [
|
||||
initElemElement(HI_GOTHIC_A),
|
||||
initElemElement(LO_GOTHIC_A),
|
||||
|
|
@ -367,7 +381,7 @@ describe('Test of ElementString file', () => {
|
|||
});
|
||||
it('can handle ElemElement of string type', () => {
|
||||
sections.strs.allocString = stubStrsAllocString_Str;
|
||||
const actual = ElementString.fromStrings(sections, ["𐌰𐌱𐌲",]);
|
||||
const actual = ElementString.fromStrings(sections, {}, ["𐌰𐌱𐌲",]);
|
||||
const expected = [
|
||||
initElemElement(
|
||||
new StrsItem("𐌰𐌱𐌲"),
|
||||
|
|
|
|||
|
|
@ -46,6 +46,8 @@
|
|||
#define LDML_KEYS_MOD_OTHER 0x10000
|
||||
#define LDML_KEYS_MOD_SHIFT 0x10
|
||||
#define LDML_LAYR_LIST_HARDWARE_TOUCH "touch"
|
||||
#define LDML_LAYR_MAX_MINDEVICEWIDTH 0x3E7
|
||||
#define LDML_LAYR_MIN_MINDEVICEWIDTH 0x1
|
||||
#define LDML_LENGTH_BKSP 0xC
|
||||
#define LDML_LENGTH_BKSP_ITEM 0x10
|
||||
#define LDML_LENGTH_DISP 0x10
|
||||
|
|
|
|||
|
|
@ -362,6 +362,16 @@ class Constants {
|
|||
*/
|
||||
readonly length_layr_key = 4;
|
||||
|
||||
/**
|
||||
* Minimum allowed minDeviceWidth for a layer list
|
||||
*/
|
||||
readonly layr_min_minDeviceWidth = 1;
|
||||
|
||||
/**
|
||||
* Maximum allowed minDeviceWidth for a layer list
|
||||
*/
|
||||
readonly layr_max_minDeviceWidth = 999;
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
* list section
|
||||
------------------------------------------------------------------ */
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
"license": "MIT",
|
||||
"type": "module",
|
||||
"main": "build/keyman_core_ldml.js",
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/keymanapp/keyman.git"
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"name": "@keymanapp/api-header-extractor",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {},
|
||||
"main": "build/src/index.js"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import { CompilerCallbacks } from "./compiler-callbacks.js";
|
||||
import { ObjectWithMetadata } from "./symbol-utils.js";
|
||||
import { KeymanXMLReader, XML_FILENAME_SYMBOL } from "./xml-utils.js";
|
||||
|
||||
/**
|
||||
* Abstract interface for compiler error and warning messages
|
||||
|
|
@ -206,6 +208,27 @@ export class CompilerError {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an offset from o and set event's offset field
|
||||
* @param event a compiler event, such as from functions in this class
|
||||
* @param x any object parsed from XML or with the XML_META_DATA_SYMBOL symbol copied over
|
||||
* @returns modified event object
|
||||
*/
|
||||
public static setFromMetadata(event: CompilerEvent, x?: ObjectWithMetadata): CompilerEvent {
|
||||
if(x) {
|
||||
const metadata = KeymanXMLReader.getMetaData(x) || {};
|
||||
const offset = metadata?.startIndex;
|
||||
if (offset) {
|
||||
event.offset = offset;
|
||||
}
|
||||
const filename = event.filename || metadata[XML_FILENAME_SYMBOL];
|
||||
if (filename) {
|
||||
event.filename = filename;
|
||||
}
|
||||
}
|
||||
return event;
|
||||
}
|
||||
};
|
||||
|
||||
/** @deprecated use `CompilerError.severity` instead */
|
||||
|
|
@ -423,6 +446,40 @@ export function dedentCompilerMessageDetail(event: CompilerEvent) {
|
|||
return (event.detail ?? '').replace(/^[ ]+/gm, '');
|
||||
}
|
||||
|
||||
/**
|
||||
* Convenience function for constructing CompilerEvents with line numbers.
|
||||
* Use it as below: (abbreviated as mx())
|
||||
*
|
||||
* ```js
|
||||
* // Note: Indentation makes "InvalidScanCode" line up thrice
|
||||
* static ERROR_InvalidScanCode = SevError | 0x0009;
|
||||
* // Note:
|
||||
* // 1. All parameters are passed in 'o', the context object is only used for context even if
|
||||
* // it contains redundant info.
|
||||
* // 2. No code execution within the arrow function other than the 'mx' call, string interpolation,
|
||||
* // with `${def(o.property)}` as the max complexity of interpolation.
|
||||
* static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithMetadata) => mx(
|
||||
* this.ERROR_InvalidScanCode, x,
|
||||
* `Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}'`,
|
||||
* // Note: If detail is omitted, leave the trailing comma on the prior line to leave room for it
|
||||
* `…additional markdown detail…`
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @param code Unique numeric value of the event
|
||||
* @param message A short description of the error presented to the user
|
||||
* @param context Object to be used as a source for line number information
|
||||
* @param detail Detailed Markdown-formatted description of the error
|
||||
* including references to documentation, remediation options.
|
||||
* @see CompilerMessageSpec
|
||||
* @returns the event
|
||||
*/
|
||||
export function CompilerMessageObjectSpec(code: number, context: ObjectWithMetadata, message: string, detail?: string): CompilerEvent {
|
||||
let evt = CompilerMessageSpec(code, message, detail); // constructs raw message
|
||||
evt = CompilerError.setFromMetadata(evt, context); // updates with offset from context
|
||||
return evt;
|
||||
};
|
||||
|
||||
export const CompilerMessageDef = (param: any) => String(param ?? `<param>`);
|
||||
|
||||
export const CompilerMessageSpecWithException = (code: number, message: string, exceptionVar: any, detail?: string) : CompilerEvent => ({
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ export {
|
|||
|
||||
export { defaultCompilerOptions, CompilerBaseOptions, CompilerOptions, CompilerEvent, CompilerErrorNamespace,
|
||||
CompilerErrorSeverity, CompilerCallbackOptions,
|
||||
CompilerError, CompilerMessageSpec, CompilerMessageSpecWithException, compilerErrorSeverity, CompilerErrorMask, compilerErrorSeverityName,
|
||||
CompilerError, CompilerMessageSpec, CompilerMessageObjectSpec, CompilerMessageSpecWithException, compilerErrorSeverity, CompilerErrorMask, compilerErrorSeverityName,
|
||||
compilerErrorFormatCode, CompilerMessageDef,
|
||||
compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS,
|
||||
ALL_COMPILER_LOG_FORMATS, CompilerLogFormat,
|
||||
|
|
@ -68,7 +68,7 @@ export { UrlSubpathCompilerCallback } from './utils/UrlSubpathCompilerCallback.j
|
|||
export { DeveloperUtilsMessages } from './developer-utils-messages.js';
|
||||
export * as SourceFilenamePatterns from './source-filename-patterns.js';
|
||||
export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL } from './xml-utils.js';
|
||||
export { SymbolUtils } from './symbol-utils.js';
|
||||
export { SymbolUtils, ObjectWithMetadata } from './symbol-utils.js';
|
||||
export * as LineUtils from './line-utils.js';
|
||||
export * as GitHubUrls from './github-urls.js';
|
||||
export * as CloudUrls from './cloud-urls.js';
|
||||
|
|
|
|||
|
|
@ -6,6 +6,14 @@
|
|||
* Utilities for manipulating Symbol properties
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* Any object with metadata, for example for line number errs.
|
||||
* Defined as 'any' here to reduce noise on the client side.
|
||||
* @see {@link KeymanXMLReader.getMetaData()}
|
||||
*/
|
||||
export type ObjectWithMetadata = any;
|
||||
|
||||
export class SymbolUtils {
|
||||
/**
|
||||
* Copy symbols shallowly from 'from' onto 'onto'
|
||||
|
|
@ -13,8 +21,8 @@ export class SymbolUtils {
|
|||
* @param from source for symbols
|
||||
* @returns the onto object
|
||||
*/
|
||||
public static copySymbols<T>(onto: T, from: any): T {
|
||||
const o = onto as any;
|
||||
public static copySymbols<T>(onto: T, from: ObjectWithMetadata): T {
|
||||
const o = onto as ObjectWithMetadata;
|
||||
for (const sym of Object.getOwnPropertySymbols(from)) {
|
||||
o[sym] = from[sym];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@ TODO: implement additional interfaces:
|
|||
import { VisualKeyboard, KvkFileReader, LdmlKeyboardTypes, KeymanFileTypes, KvkFileWriter } from '@keymanapp/common-types';
|
||||
import {
|
||||
CompilerCallbacks, CompilerEvent, CompilerOptions, KeymanCompiler, KeymanCompilerArtifacts,
|
||||
KeymanCompilerArtifactOptional, KeymanCompilerResult, KeymanCompilerArtifact, KvksFileReader
|
||||
KeymanCompilerArtifactOptional, KeymanCompilerResult, KeymanCompilerArtifact, KvksFileReader,
|
||||
CompilerError
|
||||
} from '@keymanapp/developer-utils';
|
||||
import * as Osk from './osk.js';
|
||||
import loadWasmHost from '../import/kmcmplib/wasm-host.js';
|
||||
|
|
@ -529,7 +530,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
|
|||
* @param rangeCount - number of ranges to allocate
|
||||
* @returns UnicodeSet accessor object, or null on failure
|
||||
*/
|
||||
public parseUnicodeSet(pattern: string, rangeCount: number) : LdmlKeyboardTypes.UnicodeSet | null {
|
||||
public parseUnicodeSet(pattern: string, rangeCount: number, x?: any) : LdmlKeyboardTypes.UnicodeSet | null {
|
||||
if(!this.verifyInitialized()) {
|
||||
/* c8 ignore next 2 */
|
||||
// verifyInitialized will set a callback if needed
|
||||
|
|
@ -562,7 +563,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
|
|||
// rc is negative: it's an error code.
|
||||
this.wasmExports.free(buf);
|
||||
// translate error code into callback
|
||||
this.callbacks.reportMessage(getUnicodeSetError(rc));
|
||||
this.callbacks.reportMessage(getUnicodeSetError(rc, x));
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -570,7 +571,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
public sizeUnicodeSet(pattern: string) : number {
|
||||
public sizeUnicodeSet(pattern: string, x?: any) : number {
|
||||
if(!this.verifyInitialized()) {
|
||||
/* c8 ignore next 2 */
|
||||
return null;
|
||||
|
|
@ -582,7 +583,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
|
|||
if (rc >= 0) {
|
||||
return rc;
|
||||
} else {
|
||||
this.callbacks.reportMessage(getUnicodeSetError(rc));
|
||||
this.callbacks.reportMessage(getUnicodeSetError(rc, x));
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
|
@ -593,7 +594,7 @@ export class KmnCompiler implements KeymanCompiler, LdmlKeyboardTypes.UnicodeSet
|
|||
* @param rc parseUnicodeSet error code
|
||||
* @returns the compiler event
|
||||
*/
|
||||
function getUnicodeSetError(rc: number) : CompilerEvent {
|
||||
function getUnicodeSetError(rc: number, x?: any) : CompilerEvent {
|
||||
// from kmcmplib.h
|
||||
const KMCMP_ERROR_SYNTAX_ERR = -1;
|
||||
const KMCMP_ERROR_HAS_STRINGS = -2;
|
||||
|
|
@ -601,16 +602,16 @@ function getUnicodeSetError(rc: number) : CompilerEvent {
|
|||
const KMCMP_FATAL_OUT_OF_RANGE = -4;
|
||||
switch(rc) {
|
||||
case KMCMP_ERROR_SYNTAX_ERR:
|
||||
return KmnCompilerMessages.Error_UnicodeSetSyntaxError();
|
||||
return KmnCompilerMessages.Error_UnicodeSetSyntaxError(x);
|
||||
case KMCMP_ERROR_HAS_STRINGS:
|
||||
return KmnCompilerMessages.Error_UnicodeSetHasStrings();
|
||||
return KmnCompilerMessages.Error_UnicodeSetHasStrings(x);
|
||||
case KMCMP_ERROR_UNSUPPORTED_PROPERTY:
|
||||
return KmnCompilerMessages.Error_UnicodeSetHasProperties();
|
||||
return KmnCompilerMessages.Error_UnicodeSetHasProperties(x);
|
||||
case KMCMP_FATAL_OUT_OF_RANGE:
|
||||
return KmnCompilerMessages.Fatal_UnicodeSetOutOfRange();
|
||||
return KmnCompilerMessages.Fatal_UnicodeSetOutOfRange(x);
|
||||
default:
|
||||
/* c8 ignore next */
|
||||
return KmnCompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`});
|
||||
return CompilerError.setFromMetadata(KmnCompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`}), x);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { KeyAddress } from "../kmw-compiler/validate-layout-file.js";
|
||||
import { kmnfile } from "../kmw-compiler/compiler-globals.js";
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls } from "@keymanapp/developer-utils";
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerEvent, CompilerMessageSpec as m, CompilerMessageObjectSpec as mx, CompilerMessageDef as def, CompilerMessageSpecWithException, KeymanUrls, ObjectWithMetadata } from "@keymanapp/developer-utils";
|
||||
|
||||
const Namespace = CompilerErrorNamespace.KmnCompiler;
|
||||
const SevInfo = CompilerErrorSeverity.Info | Namespace;
|
||||
|
|
@ -12,6 +12,9 @@ const SevFatal = CompilerErrorSeverity.Fatal | Namespace;
|
|||
// For messages from the KeymanWeb compiler, we need to construct our messages
|
||||
// slightly differently. This could be refactored in the future, as it is not
|
||||
// obvious which messages should use which function.
|
||||
// Specifically, this could perhaps use CompilerFileCallbacks which provides a
|
||||
// default filename.
|
||||
// Then, there could be a CompilerMessageSpecWithLine
|
||||
const mw = (code: number, message: string, o?: {filename?: string, line?: number}) : CompilerEvent => ({
|
||||
...m(code, message),
|
||||
filename: o?.filename ?? kmnfile,
|
||||
|
|
@ -104,9 +107,8 @@ export class KmnCompilerMessages {
|
|||
);
|
||||
|
||||
static FATAL_UnicodeSetOutOfRange = SevFatal | 0x904;
|
||||
static Fatal_UnicodeSetOutOfRange = () => CompilerMessageSpecWithException(
|
||||
this.FATAL_UnicodeSetOutOfRange,
|
||||
null,
|
||||
static Fatal_UnicodeSetOutOfRange = (x?: ObjectWithMetadata) => mx(
|
||||
this.FATAL_UnicodeSetOutOfRange, x,
|
||||
`UnicodeSet buffer was too small`,
|
||||
`Raised when caller to UnicodeSet functions provides an invalid buffer. If
|
||||
you experience this error, it should be reported to the Keyman team for
|
||||
|
|
@ -116,8 +118,8 @@ export class KmnCompilerMessages {
|
|||
// TODO: rename the following functions to Error_UsetHasStrings etc
|
||||
|
||||
static ERROR_UnicodeSetHasStrings = SevError | 0x905;
|
||||
static Error_UnicodeSetHasStrings = () => m(
|
||||
this.ERROR_UnicodeSetHasStrings,
|
||||
static Error_UnicodeSetHasStrings = (x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_UnicodeSetHasStrings, x,
|
||||
`uset contains strings, not allowed`,
|
||||
`The provided uset uses multi-character strings, (\`{}\` notation, e.g.
|
||||
\`[żġħ{ie}{għ}]\`. ). Although full UnicodeSets support strings, LDML
|
||||
|
|
@ -128,8 +130,8 @@ export class KmnCompilerMessages {
|
|||
);
|
||||
|
||||
static ERROR_UnicodeSetHasProperties = SevError | 0x906;
|
||||
static Error_UnicodeSetHasProperties = () => m(
|
||||
this.ERROR_UnicodeSetHasProperties,
|
||||
static Error_UnicodeSetHasProperties = (x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_UnicodeSetHasProperties, x,
|
||||
`uset contains properties, not allowed`,
|
||||
`The provided uset uses property notation (\`\\p{…}\` or \`[:…:]\`). LDML
|
||||
keyboards do not support Unicode properties in usets, because that would
|
||||
|
|
@ -140,8 +142,8 @@ export class KmnCompilerMessages {
|
|||
);
|
||||
|
||||
static ERROR_UnicodeSetSyntaxError = SevError | 0x907;
|
||||
static Error_UnicodeSetSyntaxError = () => m(
|
||||
this.ERROR_UnicodeSetSyntaxError,
|
||||
static Error_UnicodeSetSyntaxError = (x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_UnicodeSetSyntaxError, x,
|
||||
`uset had a Syntax Error while parsing`,
|
||||
`The provided uset has a syntax error and could not be parsed. Verify the
|
||||
format of the uset against the specification.
|
||||
|
|
|
|||
|
|
@ -136,7 +136,7 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
if (!source) {
|
||||
return null;
|
||||
}
|
||||
const kmx = await this.compile(source);
|
||||
const kmx = await this.compile(source, true);
|
||||
if (!kmx) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -352,6 +352,8 @@ export class LdmlKeyboardCompiler implements KeymanCompiler {
|
|||
* 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
|
||||
* @param postValidate - pass true if sections should run a 'validate' phase at the very end.
|
||||
* Set this to true if you aren't calling validate() separately.
|
||||
* @returns KMXPlusFile intermediate file
|
||||
*/
|
||||
public async compile(source: LDMLKeyboardXMLSourceFile, postValidate?: boolean): Promise<KMXPlus.KMXPlusFile> {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,10 @@ import { SubstitutionUse, Substitutions } from "./substitution-tracker.js";
|
|||
|
||||
export class DispCompiler extends SectionCompiler {
|
||||
static validateSubstitutions(keyboard: LDMLKeyboard.LKKeyboard, st : Substitutions): boolean {
|
||||
keyboard.displays?.display?.forEach(({ display, output }) => {
|
||||
st.addStringAndMarkerSubstitution(SubstitutionUse.match, output);
|
||||
st.addStringSubstitution(SubstitutionUse.emit, display);
|
||||
keyboard.displays?.display?.forEach((e) => {
|
||||
const { display, output } = e;
|
||||
st.addStringAndMarkerSubstitution(SubstitutionUse.match, output, e);
|
||||
st.addStringSubstitution(SubstitutionUse.emit, display, e);
|
||||
});
|
||||
// no marker references in 'id'
|
||||
return true;
|
||||
|
|
@ -31,20 +32,21 @@ export class DispCompiler extends SectionCompiler {
|
|||
const ids = new Set();
|
||||
|
||||
if (this.keyboard3.displays?.display) {
|
||||
for (const { output, keyId } of this.keyboard3.displays?.display) {
|
||||
for (const display of this.keyboard3.displays?.display) {
|
||||
const { output, keyId } = display;
|
||||
if ((output && keyId) || (!output && !keyId)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_DisplayNeedsToOrId({ output, keyId }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_DisplayNeedsToOrId({ display: display.display }, display));
|
||||
return false;
|
||||
} else if (output) {
|
||||
if (tos.has(output)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_DisplayIsRepeated({ output }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_DisplayIsRepeated({ display: display.display }, display));
|
||||
return false;
|
||||
} else {
|
||||
tos.add(output);
|
||||
}
|
||||
} else if (keyId) {
|
||||
if (ids.has(keyId)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_DisplayIsRepeated({ keyId }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_DisplayIsRepeated({ display: display.display }, display));
|
||||
return false;
|
||||
} else {
|
||||
ids.add(keyId);
|
||||
|
|
@ -60,7 +62,7 @@ export class DispCompiler extends SectionCompiler {
|
|||
const result = new Disp();
|
||||
|
||||
// displayOptions
|
||||
result.baseCharacter = sections.strs.allocString(this.keyboard3.displays?.displayOptions?.baseCharacter, {unescape: true});
|
||||
result.baseCharacter = sections.strs.allocString(this.keyboard3.displays?.displayOptions?.baseCharacter, { unescape: true, x: this.keyboard3?.displays?.displayOptions });
|
||||
|
||||
// displays
|
||||
result.disps = this.keyboard3.displays?.display.map(display => ({
|
||||
|
|
@ -68,11 +70,13 @@ export class DispCompiler extends SectionCompiler {
|
|||
stringVariables: true,
|
||||
markers: true,
|
||||
unescape: true,
|
||||
x: display,
|
||||
}, sections),
|
||||
id: sections.strs.allocString(display.keyId), // not escaped, not substituted
|
||||
id: sections.strs.allocString(display.keyId, { x: display }), // not escaped, not substituted
|
||||
display: sections.strs.allocString(display.display, {
|
||||
stringVariables: true,
|
||||
unescape: true,
|
||||
x: display,
|
||||
}, sections),
|
||||
})) || []; // TODO-LDML: need coverage for the []
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { SectionIdent, constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
import { SectionCompiler } from "./section-compiler.js";
|
||||
import { util, KMXPlus, LdmlKeyboardTypes } from "@keymanapp/common-types";
|
||||
import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils";
|
||||
import { CompilerCallbacks, LDMLKeyboard, ObjectWithMetadata } from "@keymanapp/developer-utils";
|
||||
import { VarsCompiler } from './vars.js';
|
||||
import { LdmlCompilerMessages } from './ldml-compiler-messages.js';
|
||||
|
||||
|
|
@ -33,13 +33,26 @@ export class StrsCompiler extends EmptyCompiler {
|
|||
public postValidate(section?: KMXPlus.Section): boolean {
|
||||
const strs = <KMXPlus.Strs>section;
|
||||
|
||||
/** attempt to find a context object for the string */
|
||||
function findContextForString(s: string): ObjectWithMetadata {
|
||||
// try exact match
|
||||
for(const str of strs.strings) {
|
||||
if (str.value == s) return str.context;
|
||||
}
|
||||
// try substring match
|
||||
for(const str of strs.strings) {
|
||||
if (str.value.includes(s)) return str.context;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
if (strs) {
|
||||
const badStringAnalyzer = new util.BadStringAnalyzer();
|
||||
const CONTAINS_MARKER_REGEX = new RegExp(LdmlKeyboardTypes.MarkerParser.ANY_MARKER_MATCH);
|
||||
for (let s of strs.allProcessedStrings.values()) {
|
||||
// stop at the first denormalized string
|
||||
if (!util.isNormalized(s)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Warn_StringDenorm({s}));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Warn_StringDenorm({s}, findContextForString(s)));
|
||||
}
|
||||
// replace all \\uXXXX with the actual code point.
|
||||
// this lets us analyze whether there are PUA, unassigned, etc.
|
||||
|
|
@ -63,16 +76,19 @@ export class StrsCompiler extends EmptyCompiler {
|
|||
const illegals = m.get(util.BadStringType.illegal);
|
||||
if (puas) {
|
||||
const [count, lowestCh] = [puas.size, Array.from(puas.values()).sort((a, b) => a - b)[0]];
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_PUACharacters({ count, lowestCh }))
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_PUACharacters({ count, lowestCh: util.describeCodepoint(lowestCh) },
|
||||
findContextForString(String.fromCodePoint(lowestCh))));
|
||||
}
|
||||
if (unassigneds) {
|
||||
const [count, lowestCh] = [unassigneds.size, Array.from(unassigneds.values()).sort((a, b) => a - b)[0]];
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Warn_UnassignedCharacters({ count, lowestCh }))
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Warn_UnassignedCharacters({ count, lowestCh: util.describeCodepoint(lowestCh) },
|
||||
findContextForString(String.fromCodePoint(lowestCh))));
|
||||
}
|
||||
if (illegals) {
|
||||
// do this last, because we will return false.
|
||||
const [count, lowestCh] = [illegals.size, Array.from(illegals.values()).sort((a, b) => a - b)[0]];
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalCharacters({ count, lowestCh }))
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalCharacters({ count, lowestCh: util.describeCodepoint(lowestCh) },
|
||||
findContextForString(String.fromCodePoint(lowestCh))));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ export class KeysCompiler extends SectionCompiler {
|
|||
for (const keyId of usedKeys.values()) {
|
||||
const key = keyBag.get(keyId);
|
||||
if (!key) continue; // key not found is handled elsewhere.
|
||||
st.addStringAndMarkerSubstitution(SubstitutionUse.emit, key.output);
|
||||
st.addStringAndMarkerSubstitution(SubstitutionUse.emit, key.output, key);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
|
@ -324,15 +324,16 @@ export class KeysCompiler extends SectionCompiler {
|
|||
|
||||
// allocate the in-memory <flick id=…>
|
||||
const flicks: KeysFlicks = new KeysFlicks(
|
||||
sections.strs.allocString(flickId)
|
||||
sections.strs.allocString(flickId, { x: flick })
|
||||
);
|
||||
|
||||
// add data from each segment
|
||||
for (const { keyId, directions } of flick.flickSegment) {
|
||||
const keyIdStr = sections.strs.allocString(keyId);
|
||||
for (const flickSegment of flick.flickSegment) {
|
||||
const { keyId, directions } = flickSegment;
|
||||
const keyIdStr = sections.strs.allocString(keyId, { x: flickSegment });
|
||||
const directionsList: ListItem = sections.list.allocListFromSpaces(
|
||||
directions,
|
||||
{ },
|
||||
{ x: flickSegment },
|
||||
sections);
|
||||
flicks.flicks.push({
|
||||
directions: directionsList,
|
||||
|
|
@ -369,20 +370,20 @@ export class KeysCompiler extends SectionCompiler {
|
|||
if (!!gap) {
|
||||
flags |= constants.keys_key_flags_gap;
|
||||
}
|
||||
const id = sections.strs.allocString(key.id);
|
||||
const id = sections.strs.allocString(key.id, { x: key });
|
||||
const longPress: ListItem = sections.list.allocListFromSpaces(
|
||||
longPressKeyIds, {},
|
||||
longPressKeyIds, { x: key },
|
||||
sections);
|
||||
|
||||
const longPressDefault = sections.strs.allocString(longPressDefaultKeyId,
|
||||
{},
|
||||
{ x: key },
|
||||
sections);
|
||||
|
||||
const multiTap: ListItem = sections.list.allocListFromSpaces(
|
||||
multiTapKeyIds,
|
||||
{},
|
||||
{ x: key },
|
||||
sections);
|
||||
const keySwitch = sections.strs.allocString(layerId); // 'switch' is a reserved word
|
||||
const keySwitch = sections.strs.allocString(layerId, { x: key }); // 'switch' is a reserved word
|
||||
|
||||
const toRaw = output;
|
||||
|
||||
|
|
@ -395,6 +396,7 @@ export class KeysCompiler extends SectionCompiler {
|
|||
unescape: true,
|
||||
singleOk: true,
|
||||
nfd: true,
|
||||
x: key,
|
||||
},
|
||||
sections);
|
||||
if (!to.isOneChar) {
|
||||
|
|
@ -461,7 +463,7 @@ export class KeysCompiler extends SectionCompiler {
|
|||
const { modifiers } = layer;
|
||||
if (!validModifier(modifiers)) {
|
||||
this.callbacks.reportMessage(
|
||||
LdmlCompilerMessages.Error_InvalidModifier({ modifiers, id: layer.id }, layer)
|
||||
LdmlCompilerMessages.Error_InvalidModifier({ modifiers }, layer)
|
||||
);
|
||||
valid = false;
|
||||
}
|
||||
|
|
@ -482,7 +484,7 @@ export class KeysCompiler extends SectionCompiler {
|
|||
LdmlCompilerMessages.Error_RowOnHardwareLayerHasTooManyKeys({
|
||||
row: y + 1,
|
||||
hardware: layers.formId,
|
||||
modifiers,
|
||||
modifiers: modifiers || 'none',
|
||||
}, row)
|
||||
);
|
||||
valid = false;
|
||||
|
|
|
|||
|
|
@ -20,27 +20,39 @@ export class LayrCompiler extends SectionCompiler {
|
|||
let valid = true;
|
||||
let totalLayerCount = 0;
|
||||
let hardwareLayers = 0;
|
||||
// let touchLayers = 0;
|
||||
let touchLayers = 0;
|
||||
const deviceWidths = new Set<number>();
|
||||
this.keyboard3.layers?.forEach((layers) => {
|
||||
const { formId } = layers;
|
||||
if (formId === 'touch') {
|
||||
// touchLayers++;
|
||||
// multiple touch layers are OK
|
||||
touchLayers++;
|
||||
totalLayerCount += layers.layer?.length;
|
||||
// TODO-LDML: check that widths are distinct
|
||||
const { minDeviceWidth } = layers;
|
||||
if (!minDeviceWidth ||
|
||||
minDeviceWidth < constants.layr_min_minDeviceWidth ||
|
||||
minDeviceWidth > constants.layr_max_minDeviceWidth ||
|
||||
Number.isNaN(Number(minDeviceWidth))) {
|
||||
valid = false;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidLayerWidth({minDeviceWidth}, layers));
|
||||
} else if (deviceWidths.has(minDeviceWidth)) {
|
||||
valid = false;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_DuplicateLayerWidth({minDeviceWidth}, layers));
|
||||
} else {
|
||||
deviceWidths.add(minDeviceWidth);
|
||||
}
|
||||
} else {
|
||||
// hardware
|
||||
hardwareLayers++;
|
||||
if (hardwareLayers > 1) {
|
||||
valid = false;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_ExcessHardware({formId}));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_ExcessHardware({formId}, layers));
|
||||
}
|
||||
}
|
||||
layers.layer.forEach((layer) => {
|
||||
const { modifiers } = layer;
|
||||
totalLayerCount++;
|
||||
if (!validModifier(modifiers)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidModifier({ modifiers, id: layer.id }, layer));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidModifier({ modifiers }, layer));
|
||||
valid = false;
|
||||
}
|
||||
});
|
||||
|
|
@ -48,7 +60,7 @@ export class LayrCompiler extends SectionCompiler {
|
|||
if (totalLayerCount === 0) { // TODO-LDML: does not validate touch layers yet
|
||||
// no layers seen anywhere
|
||||
valid = false;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement());
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(this.keyboard3?.layers[0]));
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
|
|
@ -57,13 +69,13 @@ export class LayrCompiler extends SectionCompiler {
|
|||
const sect = new Layr();
|
||||
|
||||
sect.lists = this.keyboard3.layers.map((layers) => {
|
||||
const hardware = sections.strs.allocString(layers.formId);
|
||||
const hardware = sections.strs.allocString(layers.formId, {x:layers});
|
||||
// Already validated in validate
|
||||
const layerEntries = [];
|
||||
for (const layer of layers.layer) {
|
||||
const rows = layer.row.map((row) => {
|
||||
const erow: LayrRow = {
|
||||
keys: row.keys.trim().split(/[ \t]+/).map((id) => sections.strs.allocString(id)),
|
||||
keys: row.keys.trim().split(/[ \t]+/).map((id) => sections.strs.allocString(id, { x: row })),
|
||||
};
|
||||
// include linenumber info for row
|
||||
return SectionCompiler.copySymbols(erow, row);
|
||||
|
|
@ -72,7 +84,7 @@ export class LayrCompiler extends SectionCompiler {
|
|||
// push a layer entry for each modifier set
|
||||
for (const mod of mods) {
|
||||
layerEntries.push({
|
||||
id: sections.strs.allocString(layer.id),
|
||||
id: sections.strs.allocString(layer.id, {x:layer}),
|
||||
mod,
|
||||
rows,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
import { util } from "@keymanapp/common-types";
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def, XML_FILENAME_SYMBOL, CompilerEvent, KeymanXMLReader } from '@keymanapp/developer-utils';
|
||||
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageObjectSpec as mx, CompilerMessageSpec as m, CompilerMessageDef as def, ObjectWithMetadata } from '@keymanapp/developer-utils';
|
||||
// const SevInfo = CompilerErrorSeverity.Info | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
const SevHint = CompilerErrorSeverity.Hint | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
const SevWarn = CompilerErrorSeverity.Warn | CompilerErrorNamespace.LdmlKeyboardCompiler;
|
||||
|
|
@ -9,50 +8,6 @@ const SevError = CompilerErrorSeverity.Error | CompilerErrorNamespace.LdmlKeyboa
|
|||
// sub-numberspace for transform errors
|
||||
const SevErrorTransform = SevError | 0xF00;
|
||||
|
||||
/**
|
||||
* Any object with metadata, for line number errs.
|
||||
* Could be for example an LKKeys or KMXPlus.KeysKeys object.
|
||||
* Defined as 'any' here to reduce noise on the client side.
|
||||
* @see {@link KeymanXMLReader.getMetaData()}
|
||||
*/
|
||||
type ObjectWithMetadata = any;
|
||||
|
||||
/**
|
||||
* Convenience function for constructing CompilerEvents with line numbers.
|
||||
* Use it as below: (abbreviated as mx())
|
||||
*
|
||||
* ```js
|
||||
* // Note: Indentation makes "InvalidScanCode" line up thrice
|
||||
* static ERROR_InvalidScanCode = SevError | 0x0009;
|
||||
* // Note:
|
||||
* // 1. All parameters are passed in 'o', the context object is only used for context even if
|
||||
* // it contains redundant info.
|
||||
* // 2. No code execution within the arrow function other than the 'mx' call, string interpolation,
|
||||
* // with `${def(o.property)}` as the max complexity of interpolation.
|
||||
* static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithMetadata) => mx(
|
||||
* this.ERROR_InvalidScanCode, x,
|
||||
* `Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}'`,
|
||||
* // Note: If detail is omitted, leave the trailing comma on the prior line to leave room for it
|
||||
* `…additional markdown detail…`
|
||||
* );
|
||||
* ```
|
||||
*
|
||||
* @param code Unique numeric value of the event
|
||||
* @param message A short description of the error presented to the user
|
||||
* @param context Object to be used as a source for line number information
|
||||
* @param detail Detailed Markdown-formatted description of the error
|
||||
* including references to documentation, remediation options.
|
||||
* @see CompilerMessageSpec
|
||||
* @returns
|
||||
*/
|
||||
function CompilerMessageObjectSpec(code: number, context: ObjectWithMetadata, message: string, detail?: string): CompilerEvent {
|
||||
let evt = m(code, message, detail); // constructs raw message
|
||||
evt = LdmlCompilerMessages.offset(evt, context); // updates with offset from context
|
||||
return evt;
|
||||
};
|
||||
|
||||
const mx = CompilerMessageObjectSpec;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
|
|
@ -64,7 +19,10 @@ export class LdmlCompilerMessages {
|
|||
);
|
||||
|
||||
static ERROR_InvalidLocale = SevError | 0x0002;
|
||||
static Error_InvalidLocale = (o:{tag: string}) => m(this.ERROR_InvalidLocale, `Invalid BCP 47 locale form '${def(o.tag)}'`);
|
||||
static Error_InvalidLocale = (o:{tag: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_InvalidLocale, x,
|
||||
`Invalid BCP 47 locale form '${def(o.tag)}'`,
|
||||
);
|
||||
|
||||
static ERROR_HardwareLayerHasTooManyRows = SevError | 0x0003;
|
||||
static Error_HardwareLayerHasTooManyRows = (x?: ObjectWithMetadata) => mx(
|
||||
|
|
@ -75,7 +33,7 @@ export class LdmlCompilerMessages {
|
|||
static ERROR_RowOnHardwareLayerHasTooManyKeys = SevError | 0x0004;
|
||||
static Error_RowOnHardwareLayerHasTooManyKeys = (o: { row: number, hardware: string, modifiers: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_RowOnHardwareLayerHasTooManyKeys, x,
|
||||
`Row #${def(o.row)} on 'hardware' ${def(o.hardware)} layer for modifier ${o.modifiers || 'none'} has too many keys`,
|
||||
`Row #${def(o.row)} on 'hardware' ${def(o.hardware)} layer for modifier ${def(o.modifiers)} has too many keys`,
|
||||
);
|
||||
|
||||
static ERROR_KeyNotFoundInKeyBag = SevError | 0x0005;
|
||||
|
|
@ -85,16 +43,21 @@ export class LdmlCompilerMessages {
|
|||
);
|
||||
|
||||
static HINT_OneOrMoreRepeatedLocales = SevHint | 0x0006;
|
||||
static Hint_OneOrMoreRepeatedLocales = () =>
|
||||
m(this.HINT_OneOrMoreRepeatedLocales, `After minimization, one or more locales is repeated and has been removed`);
|
||||
static Hint_OneOrMoreRepeatedLocales = (x?: ObjectWithMetadata) => mx(
|
||||
this.HINT_OneOrMoreRepeatedLocales, x,
|
||||
`After minimization, one or more locales is repeated and has been removed`,
|
||||
);
|
||||
|
||||
// This is the only allowed use of m() vs mx() in this file, all the others take context.
|
||||
static ERROR_InvalidFile = SevError | 0x0007;
|
||||
static Error_InvalidFile = (o:{errorText: string}) =>
|
||||
m(this.ERROR_InvalidFile, `The source file has an invalid structure: ${def(o.errorText)}`);
|
||||
|
||||
static HINT_LocaleIsNotMinimalAndClean = SevHint | 0x0008;
|
||||
static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}) =>
|
||||
m(this.HINT_LocaleIsNotMinimalAndClean, `Locale '${def(o.sourceLocale)}' is not minimal or correctly formatted and should be '${def(o.locale)}'`);
|
||||
static Hint_LocaleIsNotMinimalAndClean = (o:{sourceLocale: string, locale: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.HINT_LocaleIsNotMinimalAndClean, x,
|
||||
`Locale '${def(o.sourceLocale)}' is not minimal or correctly formatted and should be '${def(o.locale)}'`,
|
||||
);
|
||||
|
||||
static ERROR_InvalidScanCode = SevError | 0x0009;
|
||||
static Error_InvalidScanCode = (o: { codes: string, id: string }, x?: ObjectWithMetadata) => mx(
|
||||
|
|
@ -109,15 +72,16 @@ export class LdmlCompilerMessages {
|
|||
);
|
||||
|
||||
static ERROR_GestureKeyNotFoundInKeyBag = SevError | 0x000B;
|
||||
static Error_GestureKeyNotFoundInKeyBag = (o:{keyId: string, parentKeyId: string, attribute: string}, x?: ObjectWithMetadata) =>
|
||||
mx(
|
||||
static Error_GestureKeyNotFoundInKeyBag = (o:{keyId: string, parentKeyId: string, attribute: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_GestureKeyNotFoundInKeyBag, x,
|
||||
`Key '${def(o.keyId)}' not found in key bag, referenced from other '${def(o.parentKeyId)}' in ${def(o.attribute)}`,
|
||||
);
|
||||
|
||||
static HINT_NoDisplayForMarker = SevHint | 0x000C;
|
||||
static Hint_NoDisplayForMarker = (o: { id: string }) =>
|
||||
m(this.HINT_NoDisplayForMarker, `Key element with id "${def(o.id)}" has only marker output, but there is no matching display element by output or keyId. Keycap may be blank.`);
|
||||
static Hint_NoDisplayForMarker = (o: { id: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.HINT_NoDisplayForMarker, x,
|
||||
`Key element with id "${def(o.id)}" has only marker output, but there is no matching display element by output or keyId. Keycap may be blank.`,
|
||||
);
|
||||
|
||||
static ERROR_InvalidVersion = SevError | 0x000D;
|
||||
static Error_InvalidVersion = (o: { version: string; }, x?: ObjectWithMetadata) => mx(
|
||||
|
|
@ -134,29 +98,22 @@ export class LdmlCompilerMessages {
|
|||
);
|
||||
|
||||
static ERROR_MustBeAtLeastOneLayerElement = SevError | 0x000E;
|
||||
static Error_MustBeAtLeastOneLayerElement = () =>
|
||||
m(this.ERROR_MustBeAtLeastOneLayerElement, `The source file must contain at least one layer element.`);
|
||||
static Error_MustBeAtLeastOneLayerElement = (x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_MustBeAtLeastOneLayerElement, x,
|
||||
`The source file must contain at least one layer element.`,
|
||||
);
|
||||
|
||||
static HINT_NoDisplayForSwitch = SevHint | 0x000F;
|
||||
static Hint_NoDisplayForSwitch = (o: { id: string }) =>
|
||||
m(this.HINT_NoDisplayForSwitch, `Key element with id "${def(o.id)}" is a layer switch key, but there is no matching display element by keyId. Keycap may be blank.`);
|
||||
|
||||
/** annotate the to= or id= entry */
|
||||
private static outputOrKeyId(o:{output?: string, keyId?: string}) {
|
||||
if (o.output && o.keyId) {
|
||||
return `output='${o.output}' keyId='${o.keyId}'`;
|
||||
} else if(o.keyId) {
|
||||
return `keyId='${o.keyId}'`;
|
||||
} else if (o.output) {
|
||||
return `output='${o.output}'`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
static Hint_NoDisplayForSwitch = (o: { id: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.HINT_NoDisplayForSwitch, x,
|
||||
`Key element with id "${def(o.id)}" is a layer switch key, but there is no matching display element by keyId. Keycap may be blank.`,
|
||||
);
|
||||
|
||||
static ERROR_DisplayIsRepeated = SevError | 0x0010;
|
||||
static Error_DisplayIsRepeated = (o:{output?: string, keyId?: string}) =>
|
||||
m(this.ERROR_DisplayIsRepeated, `display ${LdmlCompilerMessages.outputOrKeyId(o)} has more than one display entry.`);
|
||||
static Error_DisplayIsRepeated = (o:{display?: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_DisplayIsRepeated, x,
|
||||
`display display='${def(o.display)}' refers to the same keyId or output as another entry.`,
|
||||
);
|
||||
|
||||
static ERROR_KeyMissingToGapOrSwitch = SevError | 0x0011;
|
||||
static Error_KeyMissingToGapOrSwitch = (o:{keyId: string}, x?: ObjectWithMetadata) => mx(
|
||||
|
|
@ -165,8 +122,10 @@ export class LdmlCompilerMessages {
|
|||
);
|
||||
|
||||
static ERROR_ExcessHardware = SevError | 0x0012;
|
||||
static Error_ExcessHardware = (o:{formId: string}) => m(this.ERROR_ExcessHardware,
|
||||
`layers formId=${def(o.formId)}: Can only have one non-'touch' element`);
|
||||
static Error_ExcessHardware = (o:{formId: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_ExcessHardware, x,
|
||||
`layers formId=${def(o.formId)}: Can only have one non-'touch' element`,
|
||||
);
|
||||
|
||||
static ERROR_InvalidHardware = SevError | 0x0013;
|
||||
static Error_InvalidHardware = (o: { formId: string }, x?: ObjectWithMetadata) => mx(
|
||||
|
|
@ -174,18 +133,10 @@ export class LdmlCompilerMessages {
|
|||
`layers has invalid value formId=${def(o.formId)}`,
|
||||
);
|
||||
|
||||
private static layerIdOrEmpty(layer : string) {
|
||||
if (layer) {
|
||||
return ` on layer id=${def(layer)}`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
static ERROR_InvalidModifier = SevError | 0x0014;
|
||||
static Error_InvalidModifier = (o:{modifiers: string, id: string}, x?: ObjectWithMetadata) => mx(
|
||||
static Error_InvalidModifier = (o:{modifiers: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_InvalidModifier, x,
|
||||
`layer has invalid modifiers='${def(o.modifiers)}'` + LdmlCompilerMessages.layerIdOrEmpty(o.id),
|
||||
`layer has invalid modifiers='${def(o.modifiers)}'`,
|
||||
);
|
||||
|
||||
static ERROR_MissingFlicks = SevError | 0x0015;
|
||||
|
|
@ -202,20 +153,28 @@ export class LdmlCompilerMessages {
|
|||
|
||||
// Not hit due to XML parsing
|
||||
static ERROR_InvalidTransformsType = SevError | 0x0018;
|
||||
static Error_InvalidTransformsType = (o:{types: string[]}) =>
|
||||
m(this.ERROR_InvalidTransformsType, `Invalid transforms types: '${def(o.types?.join(','))}'`);
|
||||
static Error_InvalidTransformsType = (o:{type: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_InvalidTransformsType, x,
|
||||
`Invalid transforms type: '${def(o.type)}'`,
|
||||
);
|
||||
|
||||
static ERROR_DuplicateTransformsType = SevError | 0x0019;
|
||||
static Error_DuplicateTransformsType = (o:{types: string[]}) =>
|
||||
m(this.ERROR_DuplicateTransformsType, `Duplicate transforms types: '${def(o.types?.join(','))}'`);
|
||||
static Error_DuplicateTransformsType = (o:{type: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_DuplicateTransformsType, x,
|
||||
`Duplicate transforms type: '${def(o.type)}'`,
|
||||
);
|
||||
|
||||
static ERROR_MixedTransformGroup = SevError | 0x001A;
|
||||
static Error_MixedTransformGroup = () =>
|
||||
m(this.ERROR_MixedTransformGroup, `transformGroup cannot contain both reorder and transform elements`);
|
||||
static Error_MixedTransformGroup = (x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_MixedTransformGroup, x,
|
||||
`transformGroup cannot contain both reorder and transform elements`,
|
||||
);
|
||||
|
||||
static ERROR_EmptyTransformGroup = SevError | 0x001B;
|
||||
static Error_EmptyTransformGroup = () =>
|
||||
m(this.ERROR_EmptyTransformGroup, `transformGroup must have either reorder or transform elements`);
|
||||
static Error_EmptyTransformGroup = (x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_EmptyTransformGroup, x,
|
||||
`transformGroup must have either reorder or transform elements`,
|
||||
);
|
||||
|
||||
static ERROR_MissingStringVariable = SevError | 0x001C;
|
||||
static Error_MissingStringVariable = (o:{id: string}, x?: ObjectWithMetadata) => mx(
|
||||
|
|
@ -224,12 +183,16 @@ export class LdmlCompilerMessages {
|
|||
);
|
||||
|
||||
static ERROR_MissingSetVariable = SevError | 0x001D;
|
||||
static Error_MissingSetVariable = (o:{id: string}) =>
|
||||
m(this.ERROR_MissingSetVariable, `Reference to undefined set variable: \$[${def(o.id)}]`);
|
||||
static Error_MissingSetVariable = (o:{id: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_MissingSetVariable, x,
|
||||
`Reference to undefined set variable: \$[${def(o.id)}]`,
|
||||
);
|
||||
|
||||
static ERROR_MissingUnicodeSetVariable = SevError | 0x001E;
|
||||
static Error_MissingUnicodeSetVariable = (o:{id: string}) =>
|
||||
m(this.ERROR_MissingUnicodeSetVariable, `Reference to undefined UnicodeSet variable: \$[${def(o.id)}]`);
|
||||
static Error_MissingUnicodeSetVariable = (o:{id: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_MissingUnicodeSetVariable, x,
|
||||
`Reference to undefined UnicodeSet variable: \$[${def(o.id)}]`,
|
||||
);
|
||||
|
||||
static ERROR_NeedSpacesBetweenSetVariables = SevError | 0x001F;
|
||||
static Error_NeedSpacesBetweenSetVariables = (o:{item: string}, x?: ObjectWithMetadata) => mx(
|
||||
|
|
@ -244,74 +207,107 @@ export class LdmlCompilerMessages {
|
|||
);
|
||||
|
||||
static ERROR_MissingMarkers = SevError | 0x0021;
|
||||
static Error_MissingMarkers = (o: { ids: string[] }) =>
|
||||
m(this.ERROR_MissingMarkers, `Markers used for matching but not defined: ${def(o.ids?.join(','))}`);
|
||||
static Error_MissingMarkers = (o: { ids: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_MissingMarkers, x,
|
||||
`Markers used for matching but not defined: ${def(o.ids)}`
|
||||
);
|
||||
|
||||
static ERROR_DisplayNeedsToOrId = SevError | 0x0022;
|
||||
static Error_DisplayNeedsToOrId = (o:{output?: string, keyId?: string}) =>
|
||||
m(this.ERROR_DisplayNeedsToOrId, `display ${LdmlCompilerMessages.outputOrKeyId(o)} needs output= or keyId=, but not both`);
|
||||
static Error_DisplayNeedsToOrId = (o:{display?: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_DisplayNeedsToOrId, x,
|
||||
`display display='${def(o.display)}' needs output= or keyId=, but not both`,
|
||||
);
|
||||
|
||||
static HINT_PUACharacters = SevHint | 0x0023;
|
||||
static Hint_PUACharacters = (o: { count: number, lowestCh: number }) =>
|
||||
m(this.HINT_PUACharacters, `File contains ${def(o.count)} PUA character(s), including ${util.describeCodepoint(o.lowestCh)}`);
|
||||
static Hint_PUACharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.HINT_PUACharacters, x,
|
||||
`File contains ${def(o.count)} PUA character(s), including ${def(o.lowestCh)}`,
|
||||
);
|
||||
|
||||
static WARN_UnassignedCharacters = SevWarn | 0x0024;
|
||||
static Warn_UnassignedCharacters = (o: { count: number, lowestCh: number }) =>
|
||||
m(this.WARN_UnassignedCharacters, `File contains ${def(o.count)} unassigned character(s), including ${util.describeCodepoint(o.lowestCh)}`);
|
||||
static Warn_UnassignedCharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.WARN_UnassignedCharacters, x,
|
||||
`File contains ${def(o.count)} unassigned character(s), including ${def(o.lowestCh)}`,
|
||||
);
|
||||
|
||||
static ERROR_IllegalCharacters = SevError | 0x0025;
|
||||
static Error_IllegalCharacters = (o: { count: number, lowestCh: number }) =>
|
||||
m(this.ERROR_IllegalCharacters, `File contains ${def(o.count)} illegal character(s), including ${util.describeCodepoint(o.lowestCh)}`);
|
||||
static Error_IllegalCharacters = (o: { count: number, lowestCh: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_IllegalCharacters, x,
|
||||
`File contains ${def(o.count)} illegal character(s), including ${def(o.lowestCh) }`,
|
||||
);
|
||||
|
||||
static HINT_CharClassImplicitDenorm = SevHint | 0x0026;
|
||||
static Hint_CharClassImplicitDenorm = (o: { lowestCh: number }) =>
|
||||
m(this.HINT_CharClassImplicitDenorm, `File has character classes which span non-NFD character(s), including ${util.describeCodepoint(o.lowestCh)}. These will not match any text.`);
|
||||
static Hint_CharClassImplicitDenorm = (o: { lowestCh: number }, x?: ObjectWithMetadata) => mx(
|
||||
this.HINT_CharClassImplicitDenorm, x,
|
||||
`File has character classes which span non-NFD character(s), including ${def(o.lowestCh)}. These will not match any text.`,
|
||||
);
|
||||
|
||||
static WARN_CharClassExplicitDenorm = SevWarn | 0x0027;
|
||||
static Warn_CharClassExplicitDenorm = (o: { lowestCh: number }) =>
|
||||
m(this.WARN_CharClassExplicitDenorm, `File has character classes which include non-NFD characters(s), including ${util.describeCodepoint(o.lowestCh)}. These will not match any text.`);
|
||||
static Warn_CharClassExplicitDenorm = (o: { lowestCh: number }, x?: ObjectWithMetadata) => mx(
|
||||
this.WARN_CharClassExplicitDenorm, x,
|
||||
`File has character classes which include non-NFD characters(s), including ${def(o.lowestCh)}. These will not match any text.`,
|
||||
);
|
||||
|
||||
static ERROR_UnparseableReorderSet = SevError | 0x0028;
|
||||
static Error_UnparseableReorderSet = (o: { from: string, set: string }) =>
|
||||
m(this.ERROR_UnparseableReorderSet, `Illegal UnicodeSet "${def(o.set)}" in reorder "${def(o.from)}`);
|
||||
// Available: 0x0028
|
||||
|
||||
static ERROR_InvalidVariableIdentifier = SevError | 0x0029;
|
||||
static Error_InvalidVariableIdentifier = (o: { id: string }) => m(
|
||||
this.ERROR_InvalidVariableIdentifier,
|
||||
static Error_InvalidVariableIdentifier = (o: { id: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_InvalidVariableIdentifier, x,
|
||||
`Invalid variable identifier "${def(o.id)}". Identifiers must be between 1 and 32 characters, and can use A-Z, a-z, 0-9, and _.`,
|
||||
);
|
||||
|
||||
static ERROR_InvalidMarkerIdentifier = SevError | 0x002A;
|
||||
static Error_InvalidMarkerIdentifier = (o: { id: string }) => m(
|
||||
this.ERROR_InvalidMarkerIdentifier,
|
||||
static Error_InvalidMarkerIdentifier = (o: { id: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_InvalidMarkerIdentifier, x,
|
||||
`Invalid marker identifier "\m{${def(o.id)}}". Identifiers must be between 1 and 32 characters, and can use A-Z, a-z, 0-9, and _.`,
|
||||
);
|
||||
|
||||
static WARN_StringDenorm = SevWarn | 0x002B;
|
||||
static Warn_StringDenorm = (o: { s: string }) =>
|
||||
m(this.WARN_StringDenorm, `File contains string "${def(o.s)}" that is neither NFC nor NFD.`);
|
||||
static Warn_StringDenorm = (o: { s: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.WARN_StringDenorm, x,
|
||||
`File contains string "${def(o.s)}" that is neither NFC nor NFD.`,
|
||||
);
|
||||
|
||||
// Available: 0x02C-0x2F
|
||||
static ERROR_DuplicateLayerWidth = SevError | 0x002C;
|
||||
static Error_DuplicateLayerWidth = (o: { minDeviceWidth: number }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_DuplicateLayerWidth, x,
|
||||
`Two or more layers have minDeviceWidth=${def(o.minDeviceWidth)}`,
|
||||
`Touch layers must have distinct widths.`
|
||||
);
|
||||
|
||||
static ERROR_InvalidLayerWidth = SevError | 0x002D;
|
||||
static Error_InvalidLayerWidth = (o: { minDeviceWidth: number }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_InvalidLayerWidth, x,
|
||||
`Invalid Layers minDeviceWidth=${def(o.minDeviceWidth)}`,
|
||||
`Width must be between 1-999 (millimeters), inclusive.` // sync with layr_max_minDeviceWidth / layr_max_maxDeviceWidth (from spec)
|
||||
);
|
||||
|
||||
// Available: 0x02E-0x2F
|
||||
|
||||
static ERROR_InvalidQuadEscape = SevError | 0x0030;
|
||||
static Error_InvalidQuadEscape = (o: { cp: number }) =>
|
||||
m(this.ERROR_InvalidQuadEscape, `Invalid escape "\\u${util.hexQuad(o?.cp || 0)}". Hint: Use "\\u{${def(o?.cp?.toString(16))}}"`);
|
||||
static Error_InvalidQuadEscape = (o: { cp: string, recommended: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_InvalidQuadEscape, x,
|
||||
`Invalid escape "${def(o.cp)}"`,
|
||||
`**Hint**: Use "${def(o.recommended)}"`,
|
||||
);
|
||||
|
||||
//
|
||||
// Transform syntax errors begin at ...F00 (SevErrorTransform)
|
||||
|
||||
// This is a bit of a catch-all and represents messages bubbling up from the underlying regex engine
|
||||
static ERROR_UnparseableTransformFrom = SevErrorTransform | 0x00;
|
||||
static Error_UnparseableTransformFrom = (o: { from: string, message: string }) =>
|
||||
m(this.ERROR_UnparseableTransformFrom, `Invalid transform from="${def(o.from)}": "${def(o.message)}"`);
|
||||
static Error_UnparseableTransformFrom = (o: { from: string, message: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_UnparseableTransformFrom, x,
|
||||
`Invalid transform from="${def(o.from)}": "${def(o.message)}"`,
|
||||
);
|
||||
|
||||
//------------------------------------------------------------------------------|
|
||||
// max length of detail message lines (checked by verifyCompilerMessagesObject) |
|
||||
//------------------------------------------------------------------------------|
|
||||
|
||||
static ERROR_IllegalTransformDollarsign = SevErrorTransform | 0x01;
|
||||
static Error_IllegalTransformDollarsign = (o: { from: string }) => m(
|
||||
this.ERROR_IllegalTransformDollarsign,
|
||||
static Error_IllegalTransformDollarsign = (o: { from: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_IllegalTransformDollarsign, x,
|
||||
`Invalid transform from="${def(o.from)}": Unescaped dollar-sign ($) is not valid transform syntax.`, `
|
||||
**Hint**: Use \`\\$\` to match a literal dollar-sign. If this precedes a
|
||||
variable name, the variable name may not be valid (A-Z, a-z, 0-9, _, 32
|
||||
|
|
@ -319,57 +315,36 @@ export class LdmlCompilerMessages {
|
|||
`);
|
||||
|
||||
static ERROR_TransformFromMatchesNothing = SevErrorTransform | 0x02;
|
||||
static Error_TransformFromMatchesNothing = (o: { from: string }) => m(
|
||||
this.ERROR_TransformFromMatchesNothing,
|
||||
static Error_TransformFromMatchesNothing = (o: { from: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_TransformFromMatchesNothing, x,
|
||||
`Invalid transfom from="${def(o.from)}": Matches an empty string.`
|
||||
);
|
||||
|
||||
static ERROR_IllegalTransformPlus = SevErrorTransform | 0x03;
|
||||
static Error_IllegalTransformPlus = (o: { from: string }) => m(
|
||||
this.ERROR_IllegalTransformPlus,
|
||||
static Error_IllegalTransformPlus = (o: { from: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_IllegalTransformPlus, x,
|
||||
`Invalid transform from="${def(o.from)}": Unescaped plus (+) is not valid transform syntax.`, `
|
||||
**Hint**: Use \`\\+\` to match a literal plus.
|
||||
`);
|
||||
|
||||
static ERROR_IllegalTransformAsterisk = SevErrorTransform | 0x04;
|
||||
static Error_IllegalTransformAsterisk = (o: { from: string }) =>m(
|
||||
this.ERROR_IllegalTransformAsterisk,
|
||||
static Error_IllegalTransformAsterisk = (o: { from: string }, x?: ObjectWithMetadata) =>mx(
|
||||
this.ERROR_IllegalTransformAsterisk, x,
|
||||
`Invalid transform from="${def(o.from)}": Unescaped asterisk (*) is not valid transform syntax.`, `
|
||||
**Hint**: Use \`\\*\` to match a literal asterisk.
|
||||
`);
|
||||
|
||||
static ERROR_IllegalTransformToUset = SevErrorTransform | 0x05;
|
||||
static Error_IllegalTransformToUset = (o: { to: string }) => m(
|
||||
this.ERROR_IllegalTransformToUset,
|
||||
static Error_IllegalTransformToUset = (o: { to: string }, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_IllegalTransformToUset, x,
|
||||
`Invalid transform to="${def(o.to)}": Set variable (\\$[…]) cannot be used in 'to=' unless part of a map.`, `
|
||||
**Hint**: If a map was meant, must use the form
|
||||
\`<transform from="($[fromSet])" to="$[1:toSet]"/>\`.
|
||||
`);
|
||||
|
||||
static ERROR_UnparseableTransformTo = SevErrorTransform | 0x06;
|
||||
static Error_UnparseableTransformTo = (o: {to: string, message: string}) => m(
|
||||
this.ERROR_UnparseableTransformTo,
|
||||
static Error_UnparseableTransformTo = (o: {to: string, message: string}, x?: ObjectWithMetadata) => mx(
|
||||
this.ERROR_UnparseableTransformTo, x,
|
||||
`Invalid transform to="${def(o.to)}": "${def(o.message)}"`,
|
||||
);
|
||||
|
||||
/**
|
||||
* Get an offset from o and set e's offset field
|
||||
* @param event a compiler event, such as from functions in this class
|
||||
* @param x any object parsed from XML or with the XML_META_DATA_SYMBOL symbol copied over
|
||||
* @returns modified event object
|
||||
*/
|
||||
static offset(event: CompilerEvent, x?: any): CompilerEvent {
|
||||
if(x) {
|
||||
const metadata = KeymanXMLReader.getMetaData(x) || {};
|
||||
const offset = metadata?.startIndex;
|
||||
if (offset) {
|
||||
event.offset = offset;
|
||||
}
|
||||
const filename = event.filename || metadata[XML_FILENAME_SYMBOL];
|
||||
if (filename) {
|
||||
event.filename = filename;
|
||||
}
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,13 +35,13 @@ export class LinterKeycaps extends Linter {
|
|||
const disp = this.findDisp(id.value, to.value);
|
||||
|
||||
if (!disp) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_NoDisplayForSwitch({ id: id.value }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_NoDisplayForSwitch({ id: id.value }, key));
|
||||
}
|
||||
} else if (to.value !== '' && nonMarkerOutput === '') {
|
||||
// has output, but only markers
|
||||
const disp = this.findDisp(id.value, to.value);
|
||||
if (!disp) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_NoDisplayForMarker({ id: id.value }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_NoDisplayForMarker({ id: id.value }, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { constants } from "@keymanapp/ldml-keyboard-constants";
|
||||
import { KMXPlus } from '@keymanapp/common-types';
|
||||
import { LDMLKeyboard } from '@keymanapp/developer-utils';
|
||||
import { LDMLKeyboard, ObjectWithMetadata } from '@keymanapp/developer-utils';
|
||||
import { SectionCompiler } from "./section-compiler.js";
|
||||
import { LdmlCompilerMessages } from "./ldml-compiler-messages.js";
|
||||
|
||||
|
|
@ -21,7 +21,17 @@ export class LocaCompiler extends SectionCompiler {
|
|||
*/
|
||||
private getLocales =
|
||||
(keyboard: LKKeyboard) =>
|
||||
[keyboard.locale].concat(Array.isArray(keyboard.locales?.locale) ? keyboard.locales.locale.map(v => v.id) : [])
|
||||
[keyboard.locale].concat(Array.isArray(keyboard.locales?.locale) ? keyboard.locales.locale.map(v => v.id) : []);
|
||||
|
||||
/**
|
||||
* Attempt to locate context metadata for a locale, for error messages
|
||||
* @param loc locale tag
|
||||
* @returns context metadata or null
|
||||
*/
|
||||
private contextForLocale(loc: string) : ObjectWithMetadata {
|
||||
if (loc == this.keyboard3.locale) return this.keyboard3;
|
||||
return this.keyboard3?.locales;
|
||||
}
|
||||
|
||||
public validate(): boolean {
|
||||
let valid = true;
|
||||
|
|
@ -31,7 +41,7 @@ export class LocaCompiler extends SectionCompiler {
|
|||
new Intl.Locale(tag);
|
||||
} catch(e) {
|
||||
if(e instanceof RangeError) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidLocale({tag}));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidLocale({tag}, this.contextForLocale(tag)));
|
||||
valid = false;
|
||||
} else {
|
||||
/* c8 ignore next 2 */
|
||||
|
|
@ -51,7 +61,8 @@ export class LocaCompiler extends SectionCompiler {
|
|||
const locales = sourceLocales.map((sourceLocale: string) => {
|
||||
const locale = new Intl.Locale(sourceLocale).minimize().toString();
|
||||
if(locale != sourceLocale) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale, locale}));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_LocaleIsNotMinimalAndClean({ sourceLocale, locale },
|
||||
this.contextForLocale(sourceLocale)));
|
||||
}
|
||||
return locale;
|
||||
});
|
||||
|
|
@ -60,10 +71,10 @@ export class LocaCompiler extends SectionCompiler {
|
|||
// 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));
|
||||
result.locales = canonicalLocales.map(locale => sections.strs.allocString(locale, {x: this.contextForLocale(locale)}));
|
||||
|
||||
if(result.locales.length < locales.length) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_OneOrMoreRepeatedLocales());
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_OneOrMoreRepeatedLocales(this.keyboard3?.locales));
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -53,12 +53,18 @@ export class MetaCompiler extends SectionCompiler {
|
|||
|
||||
public compile(sections: DependencySections): Meta {
|
||||
const result = new Meta();
|
||||
result.author = sections.strs.allocString(this.keyboard3.info?.author);
|
||||
result.conform = sections.strs.allocString(this.keyboard3.conformsTo);
|
||||
result.layout = sections.strs.allocString(this.keyboard3.info?.layout);
|
||||
result.name = sections.strs.allocString(this.keyboard3.info?.name);
|
||||
result.indicator = sections.strs.allocString(this.keyboard3.info?.indicator);
|
||||
result.version = sections.strs.allocString(this.keyboard3.version?.number ?? "0.0.0");
|
||||
result.author = sections.strs.allocString(this.keyboard3.info?.author,
|
||||
{x: this.keyboard3.info});
|
||||
result.conform = sections.strs.allocString(this.keyboard3.conformsTo,
|
||||
{x: this.keyboard3});
|
||||
result.layout = sections.strs.allocString(this.keyboard3.info?.layout,
|
||||
{x: this.keyboard3.info});
|
||||
result.name = sections.strs.allocString(this.keyboard3.info?.name,
|
||||
{x: this.keyboard3.info});
|
||||
result.indicator = sections.strs.allocString(this.keyboard3.info?.indicator,
|
||||
{x: this.keyboard3.info});
|
||||
result.version = sections.strs.allocString(this.keyboard3.version?.number ?? "0.0.0",
|
||||
{x: this.keyboard3.version});
|
||||
result.settings =
|
||||
(this.keyboard3.settings?.normalization == "disabled" ? KeyboardSettings.normalizationDisabled : 0);
|
||||
return result;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { LdmlKeyboardTypes } from "@keymanapp/common-types";
|
||||
import { ObjectWithMetadata } from "@keymanapp/developer-utils";
|
||||
|
||||
/**
|
||||
* Verb for SubstitutionTracker.add()
|
||||
|
|
@ -14,7 +15,7 @@ export enum SubstitutionUse {
|
|||
variable,
|
||||
}
|
||||
|
||||
type SubstitutionSet = Set<string>;
|
||||
type SubstitutionSet = Map<string, ObjectWithMetadata>;
|
||||
|
||||
/** Tracks usage of markers */
|
||||
export class SubstitutionTracker {
|
||||
|
|
@ -28,10 +29,10 @@ export class SubstitutionTracker {
|
|||
all: SubstitutionSet;
|
||||
|
||||
constructor() {
|
||||
this.emitted = new Set<string>();
|
||||
this.consumed = new Set<string>();
|
||||
this.matched = new Set<string>();
|
||||
this.all = new Set<string>();
|
||||
this.emitted = new Map<string, ObjectWithMetadata>();
|
||||
this.consumed = new Map<string, ObjectWithMetadata>();
|
||||
this.matched = new Map<string, ObjectWithMetadata>();
|
||||
this.all = new Map<string, ObjectWithMetadata>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -39,32 +40,33 @@ export class SubstitutionTracker {
|
|||
* @param verb what kind of use we are adding
|
||||
* @param markers list of substitutions to add
|
||||
*/
|
||||
add(verb: SubstitutionUse, markers: string[]) {
|
||||
add(verb: SubstitutionUse, markers: string[], x?: ObjectWithMetadata) {
|
||||
if (!markers.length) {
|
||||
return; // skip if empty
|
||||
}
|
||||
x = x || {}; // need at least an empty object
|
||||
if (verb == SubstitutionUse.emit) {
|
||||
markers.forEach((m) => {
|
||||
this.emitted.add(m);
|
||||
this.all.add(m);
|
||||
this.emitted.set(m, x);
|
||||
this.all.set(m,x);
|
||||
});
|
||||
} else if (verb == SubstitutionUse.consume) {
|
||||
markers.forEach((m) => {
|
||||
this.consumed.add(m);
|
||||
this.all.add(m);
|
||||
this.consumed.set(m,x);
|
||||
this.all.set(m,x);
|
||||
});
|
||||
} else if (verb == SubstitutionUse.match) {
|
||||
markers.forEach((m) => {
|
||||
this.matched.add(m);
|
||||
this.all.add(m);
|
||||
this.matched.set(m,x);
|
||||
this.all.set(m,x);
|
||||
});
|
||||
} else if (verb == SubstitutionUse.variable) {
|
||||
markers.forEach((m) => {
|
||||
// we don't know, so add it to all three
|
||||
this.matched.add(m);
|
||||
this.emitted.add(m);
|
||||
this.consumed.add(m);
|
||||
this.all.add(m);
|
||||
this.matched.set(m,x);
|
||||
this.emitted.set(m,x);
|
||||
this.consumed.set(m,x);
|
||||
this.all.set(m,x);
|
||||
});
|
||||
/* c8 skip next 3 */
|
||||
} else {
|
||||
|
|
@ -75,27 +77,27 @@ export class SubstitutionTracker {
|
|||
|
||||
/** rollup of several substitution types */
|
||||
export class Substitutions {
|
||||
addSetAndStringSubtitution(verb: SubstitutionUse, str?: string) {
|
||||
this.set.add(verb, LdmlKeyboardTypes.VariableParser.allSetReferences(str));
|
||||
this.addStringAndMarkerSubstitution(verb, str);
|
||||
addSetAndStringSubtitution(verb: SubstitutionUse, str?: string, x?: ObjectWithMetadata) {
|
||||
this.set.add(verb, LdmlKeyboardTypes.VariableParser.allSetReferences(str), x);
|
||||
this.addStringAndMarkerSubstitution(verb, str, x);
|
||||
}
|
||||
/** add a string that can have string var substitutions or markers */
|
||||
addStringAndMarkerSubstitution(verb: SubstitutionUse, str?: string) {
|
||||
this.addMarkers(verb, str);
|
||||
this.addStringSubstitution(verb, str);
|
||||
addStringAndMarkerSubstitution(verb: SubstitutionUse, str?: string, x?: ObjectWithMetadata) {
|
||||
this.addMarkers(verb, str, x);
|
||||
this.addStringSubstitution(verb, str, x);
|
||||
}
|
||||
addStringSubstitution(verb: SubstitutionUse, str?: string) {
|
||||
this.string.add(verb, LdmlKeyboardTypes.VariableParser.allStringReferences(str));
|
||||
addStringSubstitution(verb: SubstitutionUse, str?: string, x?: ObjectWithMetadata) {
|
||||
this.string.add(verb, LdmlKeyboardTypes.VariableParser.allStringReferences(str), x);
|
||||
}
|
||||
/** add a string that's just markers */
|
||||
addMarkers(verb: SubstitutionUse, str?: string) {
|
||||
this.markers.add(verb, LdmlKeyboardTypes.MarkerParser.allReferences(str));
|
||||
LdmlKeyboardTypes.MarkerParser.allBrokenReferences(str).forEach(m => this.badMarkers.add(m));
|
||||
addMarkers(verb: SubstitutionUse, str?: string, x?: ObjectWithMetadata) {
|
||||
this.markers.add(verb, LdmlKeyboardTypes.MarkerParser.allReferences(str), x);
|
||||
LdmlKeyboardTypes.MarkerParser.allBrokenReferences(str).forEach(m => this.badMarkers.set(m, x));
|
||||
}
|
||||
// all valid markers
|
||||
markers: SubstitutionTracker;
|
||||
// all invalid markers
|
||||
badMarkers: Set<string>;
|
||||
badMarkers: SubstitutionSet;
|
||||
// all valid set ids
|
||||
set: SubstitutionTracker;
|
||||
// all valid string ids
|
||||
|
|
@ -108,6 +110,6 @@ export class Substitutions {
|
|||
this.set = new SubstitutionTracker();
|
||||
this.string = new SubstitutionTracker();
|
||||
this.uset = new SubstitutionTracker();
|
||||
this.badMarkers = new Set<string>();
|
||||
this.badMarkers = new Map<string, ObjectWithMetadata>();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { constants, SectionIdent } from "@keymanapp/ldml-keyboard-constants";
|
||||
import { KMXPlus, LdmlKeyboardTypes, util } from '@keymanapp/common-types';
|
||||
import { CompilerCallbacks, LDMLKeyboard } from "@keymanapp/developer-utils";
|
||||
import { CompilerCallbacks, LDMLKeyboard, ObjectWithMetadata } from "@keymanapp/developer-utils";
|
||||
import { SectionCompiler } from "./section-compiler.js";
|
||||
|
||||
import Bksp = KMXPlus.Bksp;
|
||||
|
|
@ -18,6 +18,7 @@ import { verifyValidAndUnique } from "../util/util.js";
|
|||
import { LdmlCompilerMessages } from "./ldml-compiler-messages.js";
|
||||
import { Substitutions, SubstitutionUse } from "./substitution-tracker.js";
|
||||
import { transform_from_parse, transform_to_parse } from "../util/abnf/abnf.js";
|
||||
import { StrsOptions } from "../../../../../common/web/types/src/kmx/kmx-plus/kmx-plus.js";
|
||||
|
||||
type TransformCompilerType = 'simple' | 'backspace';
|
||||
|
||||
|
|
@ -26,22 +27,24 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
static validateSubstitutions(keyboard: LDMLKeyboard.LKKeyboard, st: Substitutions): boolean {
|
||||
keyboard?.transforms?.forEach(transforms =>
|
||||
transforms.transformGroup.forEach(transformGroup => {
|
||||
transformGroup.transform?.forEach(({ to, from }) => {
|
||||
st.addSetAndStringSubtitution(SubstitutionUse.consume, from);
|
||||
st.addSetAndStringSubtitution(SubstitutionUse.emit, to);
|
||||
transformGroup.transform?.forEach((transform) => {
|
||||
const { to, from } = transform;
|
||||
st.addSetAndStringSubtitution(SubstitutionUse.consume, from, transform);
|
||||
st.addSetAndStringSubtitution(SubstitutionUse.emit, to, transform);
|
||||
const mapFrom = LdmlKeyboardTypes.VariableParser.CAPTURE_SET_REFERENCE.exec(from);
|
||||
const mapTo = LdmlKeyboardTypes.VariableParser.MAPPED_SET_REFERENCE.exec(to || '');
|
||||
if (mapFrom) {
|
||||
// add the 'from' as a match
|
||||
st.set.add(SubstitutionUse.consume, [mapFrom[1]]);
|
||||
st.set.add(SubstitutionUse.consume, [mapFrom[1]], transform);
|
||||
}
|
||||
if (mapTo) {
|
||||
// add the 'from' as a match
|
||||
st.set.add(SubstitutionUse.emit, [mapTo[1]]);
|
||||
st.set.add(SubstitutionUse.emit, [mapTo[1]], transform);
|
||||
}
|
||||
});
|
||||
transformGroup.reorder?.forEach(({ before }) => {
|
||||
st.addStringSubstitution(SubstitutionUse.consume, before);
|
||||
transformGroup.reorder?.forEach((reorder) => {
|
||||
const { before } = reorder;
|
||||
st.addStringSubstitution(SubstitutionUse.consume, before, reorder);
|
||||
});
|
||||
}));
|
||||
return true;
|
||||
|
|
@ -55,37 +58,37 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
|
||||
public validate(): boolean {
|
||||
const reportMessage = this.callbacks.reportMessage.bind(this.callbacks);
|
||||
|
||||
const ALLOWED_TYPES = ["simple", "backspace"];
|
||||
let valid = true;
|
||||
const transforms = this?.keyboard3?.transforms;
|
||||
if (transforms) {
|
||||
const types : string[] = transforms.map(({type}) => type);
|
||||
if (transforms && this.id == "tran") {
|
||||
// Only run this part in the main "tran" compiler.
|
||||
// we use this map to get back to a context object for the error message
|
||||
const typeToObject = new Map<string, LKTransforms>();
|
||||
transforms.forEach(t => typeToObject.set(t.type, t));
|
||||
const types = transforms.map(({type}) => type);
|
||||
if (!verifyValidAndUnique(types,
|
||||
types => reportMessage(LdmlCompilerMessages.Error_DuplicateTransformsType({ types })),
|
||||
new Set(['simple', 'backspace']),
|
||||
types => reportMessage(LdmlCompilerMessages.Error_InvalidTransformsType({ types })))) {
|
||||
types => types.forEach(type =>
|
||||
reportMessage(LdmlCompilerMessages.Error_DuplicateTransformsType({ type }, typeToObject.get(type)))
|
||||
),
|
||||
new Set(ALLOWED_TYPES),
|
||||
types => types.forEach(type =>
|
||||
reportMessage(LdmlCompilerMessages.Error_InvalidTransformsType({ type }, typeToObject.get(type)))
|
||||
)
|
||||
)) {
|
||||
valid = false;
|
||||
}
|
||||
|
||||
// check for mixed groups
|
||||
let mixed = false;
|
||||
let empty = false;
|
||||
transforms.forEach(({transformGroup}) => transformGroup.forEach((transformGroup) => {
|
||||
transforms.forEach(({ type, transformGroup }) => transformGroup.forEach((transformGroup) => {
|
||||
if (this.type != type) return; // only validate bskp in bksp, tran in tran, etc.
|
||||
if (transformGroup.reorder?.length && transformGroup.transform?.length) {
|
||||
mixed = true;
|
||||
}
|
||||
if (!transformGroup.reorder?.length && !transformGroup.transform?.length) {
|
||||
empty = true;
|
||||
valid = false;
|
||||
reportMessage(LdmlCompilerMessages.Error_MixedTransformGroup(transformGroup));
|
||||
} else if (!transformGroup.reorder?.length && !transformGroup.transform?.length) {
|
||||
valid = false;
|
||||
reportMessage(LdmlCompilerMessages.Error_EmptyTransformGroup(transformGroup));
|
||||
}
|
||||
}));
|
||||
if (mixed) {
|
||||
valid = false;
|
||||
reportMessage(LdmlCompilerMessages.Error_MixedTransformGroup()); // report this once
|
||||
}
|
||||
if (empty) {
|
||||
valid = false;
|
||||
reportMessage(LdmlCompilerMessages.Error_EmptyTransformGroup()); // report this once
|
||||
}
|
||||
|
||||
// TODO-LDML: linting here should check for identical 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
|
||||
|
|
@ -96,7 +99,7 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
/* c8 ignore next 4 */
|
||||
/** allocate a new TranBase subclass */
|
||||
protected newTran(): TranBase {
|
||||
throw Error(`Internal Error: newTran() not implemented`);
|
||||
throw Error(`Internal Error: TranBase.newTran() not implemented (should not be called)`);
|
||||
}
|
||||
|
||||
private compileTransforms(sections: DependencySections, transforms: LKTransforms): TranBase {
|
||||
|
|
@ -138,6 +141,8 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
}
|
||||
|
||||
private compileTransform(sections: DependencySections, transform: LKTransform) : TranTransform {
|
||||
// we have lots of strings to allocate, that will all have these options
|
||||
const stropts : StrsOptions = { x: transform };
|
||||
const result = new TranTransform();
|
||||
// setup for serializing
|
||||
result._from = transform.from;
|
||||
|
|
@ -146,10 +151,10 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
|
||||
// check for incorrect \uXXXX escapes. Do this before substituting markers or sets.
|
||||
// We run this first because it's more helpful than the ABNF.
|
||||
cookedFrom = this.checkEscapes(cookedFrom); // check for \uXXXX escapes before normalizing
|
||||
cookedFrom = this.checkEscapes(cookedFrom, transform); // check for \uXXXX escapes before normalizing
|
||||
|
||||
if (cookedFrom === '') {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_TransformFromMatchesNothing({ from: cookedFrom }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_TransformFromMatchesNothing({ from: cookedFrom }, transform));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
@ -157,11 +162,11 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
const mapFrom = LdmlKeyboardTypes.VariableParser.CAPTURE_SET_REFERENCE.exec(cookedFrom);
|
||||
const mapTo = LdmlKeyboardTypes.VariableParser.MAPPED_SET_REFERENCE.exec(transform.to || '');
|
||||
if (mapFrom && mapTo) { // TODO-LDML: error cases
|
||||
result.mapFrom = sections.strs.allocString(mapFrom[1]); // var name
|
||||
result.mapTo = sections.strs.allocString(mapTo[1]); // var name
|
||||
result.mapFrom = sections.strs.allocString(mapFrom[1], stropts); // var name
|
||||
result.mapTo = sections.strs.allocString(mapTo[1], stropts); // var name
|
||||
} else {
|
||||
result.mapFrom = sections.strs.allocString('');
|
||||
result.mapTo = sections.strs.allocString('');
|
||||
result.mapFrom = sections.strs.allocString('', stropts);
|
||||
result.mapTo = sections.strs.allocString('', stropts);
|
||||
|
||||
// validate 'to' here
|
||||
if (!this.isValidTo(transform.to || '')) {
|
||||
|
|
@ -181,7 +186,7 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
cookedFrom = util.unescapeStringToRegex(cookedFrom);
|
||||
|
||||
// check for denormalized ranges
|
||||
cookedFrom = this.checkRanges(cookedFrom); // check before normalizing
|
||||
cookedFrom = this.checkRanges(cookedFrom, transform); // check before normalizing
|
||||
|
||||
if (!sections?.meta?.normalizationDisabled) {
|
||||
// nfd here.
|
||||
|
|
@ -199,21 +204,22 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
transform_from_parse(transform.from);
|
||||
}
|
||||
} catch (e) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_UnparseableTransformFrom({ from: cookedFrom, message: e.toString() }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_UnparseableTransformFrom({ from: cookedFrom, message: e.toString() }, transform));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// run the parser here next, on the original to
|
||||
try {
|
||||
transform_to_parse(transform.to || '');
|
||||
} catch (e) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_UnparseableTransformTo({ to: transform.to || '', message: e.toString() }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_UnparseableTransformTo({ to: transform.to || '', message: e.toString() }, transform));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
// cookedFrom is cooked above, since there's some special treatment
|
||||
result.from = sections.strs.allocString(cookedFrom, {
|
||||
unescape: false,
|
||||
x: transform,
|
||||
}, sections);
|
||||
// 'to' is handled via allocString
|
||||
result.to = sections.strs.allocString(transform.to, {
|
||||
|
|
@ -221,6 +227,7 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
markers: true,
|
||||
unescape: true,
|
||||
nfd: true,
|
||||
x: transform,
|
||||
}, sections);
|
||||
return result;
|
||||
}
|
||||
|
|
@ -230,9 +237,9 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
* We have already checked that it's not a mapTo,
|
||||
* so there should not be any illegal substitutions.
|
||||
*/
|
||||
private isValidTo(to: string) : boolean {
|
||||
private isValidTo(to: string, x?: ObjectWithMetadata) : boolean {
|
||||
if (/(?<!\\)(?:\\\\)*\$\[/.test(to)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalTransformToUset({ to }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalTransformToUset({ to }, x));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -244,18 +251,18 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
* @param from the original from - for error reporting
|
||||
* @returns true if OK
|
||||
*/
|
||||
private isValidRegex(cookedFrom: string, from: string) : boolean {
|
||||
private isValidRegex(cookedFrom: string, from: string, x?: ObjectWithMetadata) : boolean {
|
||||
// check for any unescaped dollar sign here
|
||||
if (/(?<!\\)(?:\\\\)*\$/.test(cookedFrom)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalTransformDollarsign({ from }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalTransformDollarsign({ from }, x));
|
||||
return false;
|
||||
}
|
||||
if (/(?<!\\)(?:\\\\)*\*/.test(cookedFrom)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalTransformAsterisk({ from }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalTransformAsterisk({ from }, x));
|
||||
return false;
|
||||
}
|
||||
if (/(?<!\\)(?:\\\\)*\+/.test(cookedFrom)) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalTransformPlus({ from }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_IllegalTransformPlus({ from }, x));
|
||||
return false;
|
||||
}
|
||||
// Verify that the regex is syntactically valid
|
||||
|
|
@ -265,14 +272,14 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
|
||||
// does it match an empty string?
|
||||
if (rg.test('')) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_TransformFromMatchesNothing({ from }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_TransformFromMatchesNothing({ from }, x));
|
||||
return false;
|
||||
}
|
||||
} catch (e) {
|
||||
// We're exposing the internal regex error message here.
|
||||
// In the future, CLDR plans to expose the EBNF for the transform,
|
||||
// at which point we would have more precise validation prior to getting to this point.
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_UnparseableTransformFrom({ from, message: e.message }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_UnparseableTransformFrom({ from, message: e.message }, x));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -294,14 +301,14 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
result._before = reorder.before;
|
||||
result._from = reorder.from;
|
||||
result._order = reorder.order;
|
||||
if (reorder.from && this.checkEscapes(reorder.from) === null) {
|
||||
if (reorder.from && this.checkEscapes(reorder.from, reorder) === null) {
|
||||
return null; // error'ed
|
||||
}
|
||||
if (reorder.before && this.checkEscapes(reorder.before) === null) {
|
||||
if (reorder.before && this.checkEscapes(reorder.before, reorder) === null) {
|
||||
return null; // error'ed
|
||||
}
|
||||
result.elements = sections.elem.allocElementString(sections, reorder.from, reorder.order, reorder.tertiary, reorder.tertiaryBase, reorder.preBase);
|
||||
result.before = sections.elem.allocElementString(sections, reorder.before);
|
||||
result.elements = sections.elem.allocElementString(sections, {x:reorder}, reorder.from, reorder.order, reorder.tertiary, reorder.tertiaryBase, reorder.preBase);
|
||||
result.before = sections.elem.allocElementString(sections, {x:reorder}, reorder.before);
|
||||
if (!result.elements || !result.before) {
|
||||
return null; // already error'ed
|
||||
} else {
|
||||
|
|
@ -337,16 +344,21 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
* @param cookedFrom the original string
|
||||
* @returns the original string, or null if an error was reported
|
||||
*/
|
||||
private checkEscapes(cookedFrom: string): string | null {
|
||||
private checkEscapes(cookedFrom: string, transform?: ObjectWithMetadata): string | null {
|
||||
if (!cookedFrom) return cookedFrom;
|
||||
|
||||
// should not follow marker prefix, nor marker prefix with range
|
||||
const anyQuad = /(?<!\\uffff\\u0008(?:\[[0-9a-fA-F\\u-]*)?)\\u([0-9a-fA-F]{4})/g;
|
||||
|
||||
for (const [, sub] of cookedFrom.matchAll(anyQuad)) {
|
||||
for (const [matched, sub] of cookedFrom.matchAll(anyQuad)) {
|
||||
const s = util.unescapeOne(sub);
|
||||
if (s !== '\uffff' && s !== '\u0008') { // markers
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: s.codePointAt(0) }));
|
||||
const codepoint: number = s.codePointAt(0);
|
||||
const cp: string = matched; // the original match from the file
|
||||
const recommended: string = `\\u{${codepoint.toString(16)}}`;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidQuadEscape({
|
||||
cp, recommended
|
||||
}, transform));
|
||||
return null; // exit on the first error
|
||||
}
|
||||
}
|
||||
|
|
@ -359,7 +371,7 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
* @param cookedFrom input regex string
|
||||
* @returns updated 'from' string
|
||||
*/
|
||||
private checkRanges(cookedFrom: string): string {
|
||||
private checkRanges(cookedFrom: string, transform?: LKTransform): string {
|
||||
if (!cookedFrom) return cookedFrom;
|
||||
|
||||
// extract all of the potential ranges - but don't match any-markers!
|
||||
|
|
@ -408,13 +420,17 @@ export abstract class TransformCompiler<T extends TransformCompilerType, TranBas
|
|||
let needCooking = false;
|
||||
const explicitSet = rangeExplicit.analyze()?.get(util.BadStringType.denormalized);
|
||||
if (explicitSet) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Warn_CharClassExplicitDenorm({ lowestCh: explicitSet.values().next().value }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Warn_CharClassExplicitDenorm({
|
||||
lowestCh: explicitSet.values().next().value
|
||||
}, transform));
|
||||
needCooking = true;
|
||||
} else {
|
||||
// don't analyze the implicit set of THIS range, if explicit is already problematic
|
||||
const implicitSet = rangeImplicit.analyze()?.get(util.BadStringType.denormalized);
|
||||
if (implicitSet) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_CharClassImplicitDenorm({ lowestCh: implicitSet.values().next().value }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Hint_CharClassImplicitDenorm({
|
||||
lowestCh: implicitSet.values().next().value
|
||||
}, transform));
|
||||
needCooking = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -472,4 +488,8 @@ export class BkspCompiler extends TransformCompiler<'backspace', Bksp /*, BkspIt
|
|||
public get id() {
|
||||
return constants.section.bksp;
|
||||
}
|
||||
/** */
|
||||
public validate(): boolean {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { SectionIdent, constants } from "@keymanapp/ldml-keyboard-constants";
|
||||
import { KMXPlus, LdmlKeyboardTypes } from '@keymanapp/common-types';
|
||||
import { LDMLKeyboard, CompilerCallbacks } from '@keymanapp/developer-utils';
|
||||
import { LDMLKeyboard, CompilerCallbacks, ObjectWithMetadata } from '@keymanapp/developer-utils';
|
||||
import { SectionCompiler } from "./section-compiler.js";
|
||||
import Vars = KMXPlus.Vars;
|
||||
import StringVarItem = KMXPlus.StringVarItem;
|
||||
|
|
@ -40,9 +40,9 @@ export class VarsCompiler extends SectionCompiler {
|
|||
return valid;
|
||||
}
|
||||
|
||||
private validateIdentifier(id: string) {
|
||||
private validateIdentifier(id: string, x?: ObjectWithMetadata) {
|
||||
if(!id.match(LdmlKeyboardTypes.VariableParser.ID)) { // From <string> DTD
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidVariableIdentifier({id}));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidVariableIdentifier({id}, x));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
|
@ -77,7 +77,7 @@ export class VarsCompiler extends SectionCompiler {
|
|||
// Strings
|
||||
for (const e of variables.string) {
|
||||
const { id, value } = e;
|
||||
if(!this.validateIdentifier(id)) {
|
||||
if(!this.validateIdentifier(id, e)) {
|
||||
valid = false;
|
||||
continue;
|
||||
}
|
||||
|
|
@ -90,9 +90,9 @@ export class VarsCompiler extends SectionCompiler {
|
|||
allStrings.add(ref); // avoids multiple reports of same missing variable
|
||||
}
|
||||
}
|
||||
st.string.add(SubstitutionUse.variable, stringrefs);
|
||||
st.string.add(SubstitutionUse.variable, stringrefs, e);
|
||||
allStrings.add(id);
|
||||
st.addMarkers(SubstitutionUse.variable, value);
|
||||
st.addMarkers(SubstitutionUse.variable, value, e);
|
||||
}
|
||||
// Sets
|
||||
for (const e of variables.set) {
|
||||
|
|
@ -105,7 +105,7 @@ export class VarsCompiler extends SectionCompiler {
|
|||
allSets.add(id);
|
||||
// check for illegal references, here.
|
||||
const stringrefs = LdmlKeyboardTypes.VariableParser.allStringReferences(value);
|
||||
st.string.add(SubstitutionUse.variable, stringrefs);
|
||||
st.string.add(SubstitutionUse.variable, stringrefs, e);
|
||||
|
||||
// Now split into spaces.
|
||||
const items: string[] = LdmlKeyboardTypes.VariableParser.setSplitter(value);
|
||||
|
|
@ -116,7 +116,7 @@ export class VarsCompiler extends SectionCompiler {
|
|||
valid = false;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_NeedSpacesBetweenSetVariables({ item }, e));
|
||||
} else {
|
||||
st.set.add(SubstitutionUse.variable, setrefs); // the reference to a 'map'
|
||||
st.set.add(SubstitutionUse.variable, setrefs, e); // the reference to a 'map'
|
||||
}
|
||||
// TODO-LDML: Are there other illegal cases here? what about "x$[set]"?
|
||||
}
|
||||
|
|
@ -131,7 +131,7 @@ export class VarsCompiler extends SectionCompiler {
|
|||
addId(id, e);
|
||||
allUnicodeSets.add(id);
|
||||
const stringrefs = LdmlKeyboardTypes.VariableParser.allStringReferences(value);
|
||||
st.string.add(SubstitutionUse.variable, stringrefs);
|
||||
st.string.add(SubstitutionUse.variable, stringrefs, e);
|
||||
const setrefs = LdmlKeyboardTypes.VariableParser.allSetReferences(value);
|
||||
for (const id2 of setrefs) {
|
||||
if (!allUnicodeSets.has(id2)) {
|
||||
|
|
@ -140,7 +140,7 @@ export class VarsCompiler extends SectionCompiler {
|
|||
// $[set] in a UnicodeSet must be another UnicodeSet.
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_CantReferenceSetFromUnicodeSet({ id: id2 }, e));
|
||||
} else {
|
||||
st.uset.add(SubstitutionUse.variable, [id2]);
|
||||
st.uset.add(SubstitutionUse.variable, [id2], e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -157,24 +157,24 @@ export class VarsCompiler extends SectionCompiler {
|
|||
}
|
||||
|
||||
// check for any missing vars
|
||||
for (const id of st.set.all) {
|
||||
for (const [id,x] of st.set.all.entries()) {
|
||||
// note: we check the uset list also. we don't know until later which was
|
||||
// intended. collisions are handled separately.
|
||||
if (!allSets.has(id) && !allUnicodeSets.has(id)) {
|
||||
valid = false;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingSetVariable({ id }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingSetVariable({ id }, x));
|
||||
}
|
||||
}
|
||||
for (const id of st.string.all) {
|
||||
for (const [id,x] of st.string.all.entries()) {
|
||||
if (!allStrings.has(id)) {
|
||||
valid = false;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingStringVariable({ id }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingStringVariable({ id }, x));
|
||||
}
|
||||
}
|
||||
for (const id of st.uset.all) {
|
||||
for (const [id,x] of st.uset.all.entries()) {
|
||||
if (!allUnicodeSets.has(id)) {
|
||||
valid = false;
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingUnicodeSetVariable({ id }));
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingUnicodeSetVariable({ id }, x));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -198,13 +198,13 @@ export class VarsCompiler extends SectionCompiler {
|
|||
// see if there are any matched-but-not-emitted markers
|
||||
const matchedNotEmitted : Set<string> = new Set<string>();
|
||||
const mt = st.markers;
|
||||
for (const m of mt.matched.values()) {
|
||||
for (const m of mt.matched.keys()) {
|
||||
if (m === LdmlKeyboardTypes.MarkerParser.ANY_MARKER_ID) continue; // match-all marker
|
||||
if (!mt.emitted.has(m)) {
|
||||
matchedNotEmitted.add(m);
|
||||
}
|
||||
}
|
||||
for (const m of mt.consumed.values()) {
|
||||
for (const m of mt.consumed.keys()) {
|
||||
if (m === LdmlKeyboardTypes.MarkerParser.ANY_MARKER_ID) continue; // match-all marker
|
||||
if (!mt.emitted.has(m)) {
|
||||
matchedNotEmitted.add(m);
|
||||
|
|
@ -213,7 +213,9 @@ export class VarsCompiler extends SectionCompiler {
|
|||
|
||||
// report once
|
||||
if (matchedNotEmitted.size > 0) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingMarkers({ ids: Array.from(matchedNotEmitted.values()).sort() }));
|
||||
const ids = Array.from(matchedNotEmitted.values()).sort();
|
||||
const x = mt.all.get(ids[0]); // get the FIRST object for context
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_MissingMarkers({ ids: ids.join(',')}, x));
|
||||
valid = false;
|
||||
}
|
||||
|
||||
|
|
@ -222,19 +224,24 @@ export class VarsCompiler extends SectionCompiler {
|
|||
|
||||
if (!!st.badMarkers.size) {
|
||||
valid = false;
|
||||
st.badMarkers.forEach(id =>
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidMarkerIdentifier({ id })));
|
||||
for (const [id, x] of st.badMarkers.entries()) {
|
||||
this.callbacks.reportMessage(LdmlCompilerMessages.Error_InvalidMarkerIdentifier({ id }, x));
|
||||
}
|
||||
}
|
||||
|
||||
return valid;
|
||||
}
|
||||
|
||||
validateSubstitutions(keyboard: LDMLKeyboard.LKKeyboard, st : Substitutions) : boolean {
|
||||
keyboard?.variables?.string?.forEach(({value}) =>
|
||||
st.markers.add(SubstitutionUse.variable, LdmlKeyboardTypes.MarkerParser.allReferences(value)));
|
||||
keyboard?.variables?.string?.forEach((string) => {
|
||||
const {value} = string;
|
||||
st.markers.add(SubstitutionUse.variable, LdmlKeyboardTypes.MarkerParser.allReferences(value), string);
|
||||
});
|
||||
// get markers mentioned in a set
|
||||
keyboard?.variables?.set?.forEach(({ value }) =>
|
||||
LdmlKeyboardTypes.VariableParser.setSplitter(value).forEach(v => st.markers.add(SubstitutionUse.match, LdmlKeyboardTypes.MarkerParser.allReferences(v))));
|
||||
keyboard?.variables?.set?.forEach((set) => {
|
||||
const { value } = set;
|
||||
LdmlKeyboardTypes.VariableParser.setSplitter(value).forEach(v => st.markers.add(SubstitutionUse.match, LdmlKeyboardTypes.MarkerParser.allReferences(v), set));
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -260,8 +267,16 @@ export class VarsCompiler extends SectionCompiler {
|
|||
const mt = st.markers;
|
||||
|
||||
// collect all markers, excluding the match-all
|
||||
const allMarkers : string[] = Array.from(mt.all).filter(m => m !== LdmlKeyboardTypes.MarkerParser.ANY_MARKER_ID).sort();
|
||||
result.markers = sections.list.allocList(allMarkers, {}, sections);
|
||||
const allMarkers : string[] = Array.from(mt.all.keys()).filter(m => m !== LdmlKeyboardTypes.MarkerParser.ANY_MARKER_ID).sort();
|
||||
result.markers = sections.list.allocList(allMarkers, {
|
||||
// pass the first object in the listr
|
||||
x: mt.all.get(allMarkers[0]||'')
|
||||
// however, this is the string with the marker *id*. So, from the StrsCompiler
|
||||
// point of view, the value of this context would be if the marker ID had, say,
|
||||
// invalid Unicode in it for ERROR_IllegalCharacters - but, that wouldn't be a
|
||||
// valid Marker ID either.
|
||||
// So it's not likely that this context will be used, but it's available.
|
||||
}, sections);
|
||||
|
||||
// sets need to be added late, because they can refer to markers
|
||||
variables?.set?.forEach((e) =>
|
||||
|
|
|
|||
|
|
@ -120,15 +120,15 @@ export function allUsedKeyIdsInFlick(flick? : LDMLKeyboard.LKFlick) : Set<string
|
|||
* @param onInvalid callback with array of invalid values, deduped
|
||||
* @returns true if all OK
|
||||
*/
|
||||
export function verifyValidAndUnique(
|
||||
values: string[],
|
||||
onDuplicate: (duplicates: string[]) => void,
|
||||
allowed?: Set<string>,
|
||||
onInvalid?: (invalids: string[]) => void)
|
||||
export function verifyValidAndUnique<T>(
|
||||
values: T[],
|
||||
onDuplicate: (duplicates: T[]) => void,
|
||||
allowed?: Set<T>,
|
||||
onInvalid?: (invalids: T[]) => void)
|
||||
: boolean {
|
||||
const dups: string[] = [];
|
||||
const invalids: string[] = [];
|
||||
const seen = new Set<string>();
|
||||
const dups: T[] = [];
|
||||
const invalids: T[] = [];
|
||||
const seen = new Set<T>();
|
||||
for (const value of values) {
|
||||
if (allowed && !allowed.has(value)) {
|
||||
invalids.push(value);
|
||||
|
|
@ -140,7 +140,7 @@ export function verifyValidAndUnique(
|
|||
}
|
||||
}
|
||||
|
||||
function dedupedSortedArray(values: string[]) : string[] {
|
||||
function dedupedSortedArray(values: T[]) : T[] {
|
||||
return Array.from(new Set(values)).sort();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import 'mocha';
|
||||
import {assert} from 'chai';
|
||||
import hextobin from '@keymanapp/hextobin';
|
||||
import {compileKeyboard, compilerTestCallbacks, compilerTestOptions, makePathToFixture} from './helpers/index.js';
|
||||
import {compileKeyboard, compilerTestCallbacks, compilerTestOptions, makePathToFixture, scrubContextFromMessages} from './helpers/index.js';
|
||||
import { compareXml } from './helpers/compareXml.js';
|
||||
import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js';
|
||||
import { kmxToXml } from '../src/util/serialize.js';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { LdmlCompilerMessages } from '../src/main.js';
|
||||
import { util } from '@keymanapp/common-types';
|
||||
|
||||
/** Overall compiler tests */
|
||||
describe('compiler-tests', function() {
|
||||
|
|
@ -42,6 +44,25 @@ describe('compiler-tests', function() {
|
|||
assert.isNotNull(kvk?.data);
|
||||
});
|
||||
|
||||
it('should-validate-on-run compiling sections/strs/invalid-illegal.xml', async function() {
|
||||
this.timeout(4000);
|
||||
const inputFilename = makePathToFixture('sections/strs/invalid-illegal.xml');
|
||||
|
||||
// should fail validation
|
||||
const k = new LdmlKeyboardCompiler();
|
||||
await k.init(compilerTestCallbacks, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false });
|
||||
|
||||
const runOutput = await k.run(inputFilename, "invalid-illegal.kmx"); // need the exact name passed to build-fixtures
|
||||
assert.isNull(runOutput, "Expect invalid-illegal to fail to run()");
|
||||
assert.sameDeepMembers( scrubContextFromMessages(compilerTestCallbacks.messages), [
|
||||
// copied from strs.tests.ts
|
||||
// validation messages
|
||||
LdmlCompilerMessages.Error_IllegalCharacters({ count: 5, lowestCh: util.describeCodepoint(0xFDD0) }),
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: util.describeCodepoint(0xE010) }),
|
||||
]);
|
||||
});
|
||||
|
||||
|
||||
it('should-serialize-kmx', async function() {
|
||||
this.timeout(4000);
|
||||
// Let's build basic.xml
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'mocha';
|
||||
import {assert} from 'chai';
|
||||
import { DispCompiler } from '../src/compiler/disp.js';
|
||||
import { compilerTestCallbacks, loadSectionFixture, testCompilationCases } from './helpers/index.js';
|
||||
import { compilerTestCallbacks, loadSectionFixture, testCompilationCases, withOffset } from './helpers/index.js';
|
||||
import { KMXPlus } from '@keymanapp/common-types';
|
||||
import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js';
|
||||
|
||||
|
|
@ -67,25 +67,25 @@ describe('disp', function () {
|
|||
const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-dupto.xml', compilerTestCallbacks) as Disp;
|
||||
assert.isNull(disp);
|
||||
assert.equal(compilerTestCallbacks.messages.length, 1);
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayIsRepeated({ output: 'e' }));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayIsRepeated({ display: '(e)' }, withOffset(330)));
|
||||
});
|
||||
it('should reject duplicate ids', async function() {
|
||||
const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-dupid.xml', compilerTestCallbacks) as Disp;1
|
||||
assert.isNull(disp);
|
||||
assert.equal(compilerTestCallbacks.messages.length, 1);
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayIsRepeated({ keyId: 'e' }));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayIsRepeated({ display: '(e)' }, withOffset(329)));
|
||||
});
|
||||
it('should reject if neither to nor id', async function() {
|
||||
const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-none.xml', compilerTestCallbacks) as Disp;
|
||||
assert.isNull(disp);
|
||||
assert.equal(compilerTestCallbacks.messages.length, 1);
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayNeedsToOrId({}));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayNeedsToOrId({ display: '(f)'}, withOffset(182)));
|
||||
});
|
||||
it('should reject if both to and id', async function() {
|
||||
const disp = await loadSectionFixture(DispCompiler, 'sections/disp/invalid-both.xml', compilerTestCallbacks) as Disp;
|
||||
assert.isNull(disp);
|
||||
assert.equal(compilerTestCallbacks.messages.length, 1);
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayNeedsToOrId({ output: 'e', keyId: 'e' }));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_DisplayNeedsToOrId({ display: '(e)' }, withOffset(182)));
|
||||
});
|
||||
testCompilationCases(DispCompiler, [
|
||||
{
|
||||
|
|
|
|||
29
developer/src/kmc-ldml/test/fixtures/sections/layr/error-bad-width-0.xml
vendored
Normal file
29
developer/src/kmc-ldml/test/fixtures/sections/layr/error-bad-width-0.xml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
|
||||
<info name="layr-error-custom-form"/>
|
||||
<keys>
|
||||
<key id="one" output="1" />
|
||||
<key id="two" output="2" />
|
||||
<key id="three" output="3" />
|
||||
</keys>
|
||||
<layers formId="iso">
|
||||
<layer id="base">
|
||||
<row keys="one two three" />
|
||||
</layer>
|
||||
</layers>
|
||||
<layers formId="touch" minDeviceWidth="120">
|
||||
<layer id="base">
|
||||
<row keys="one" />
|
||||
<row keys="two" />
|
||||
<row keys="three" />
|
||||
</layer>
|
||||
</layers>
|
||||
<layers formId="touch" minDeviceWidth="0"> <!-- dup -->
|
||||
<layer id="base">
|
||||
<row keys="one" />
|
||||
<row keys="two" />
|
||||
<row keys="three" />
|
||||
</layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
29
developer/src/kmc-ldml/test/fixtures/sections/layr/error-bad-width-1024.xml
vendored
Normal file
29
developer/src/kmc-ldml/test/fixtures/sections/layr/error-bad-width-1024.xml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
|
||||
<info name="layr-error-custom-form"/>
|
||||
<keys>
|
||||
<key id="one" output="1" />
|
||||
<key id="two" output="2" />
|
||||
<key id="three" output="3" />
|
||||
</keys>
|
||||
<layers formId="iso">
|
||||
<layer id="base">
|
||||
<row keys="one two three" />
|
||||
</layer>
|
||||
</layers>
|
||||
<layers formId="touch" minDeviceWidth="6">
|
||||
<layer id="base">
|
||||
<row keys="one" />
|
||||
<row keys="two" />
|
||||
<row keys="three" />
|
||||
</layer>
|
||||
</layers>
|
||||
<layers formId="touch" minDeviceWidth="1024"> <!-- dup -->
|
||||
<layer id="base">
|
||||
<row keys="one" />
|
||||
<row keys="two" />
|
||||
<row keys="three" />
|
||||
</layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
22
developer/src/kmc-ldml/test/fixtures/sections/layr/error-bad-width-1500.xml
vendored
Normal file
22
developer/src/kmc-ldml/test/fixtures/sections/layr/error-bad-width-1500.xml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
|
||||
<info name="layr-error-custom-form"/>
|
||||
<keys>
|
||||
<key id="one" output="1" />
|
||||
<key id="two" output="2" />
|
||||
<key id="three" output="3" />
|
||||
</keys>
|
||||
<layers formId="iso">
|
||||
<layer id="base">
|
||||
<row keys="one two three" />
|
||||
</layer>
|
||||
</layers>
|
||||
<layers formId="touch" minDeviceWidth="1500">
|
||||
<layer id="base">
|
||||
<row keys="one" />
|
||||
<row keys="two" />
|
||||
<row keys="three" />
|
||||
</layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
22
developer/src/kmc-ldml/test/fixtures/sections/layr/error-bad-width-x.xml
vendored
Normal file
22
developer/src/kmc-ldml/test/fixtures/sections/layr/error-bad-width-x.xml
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
|
||||
<info name="layr-error-custom-form"/>
|
||||
<keys>
|
||||
<key id="one" output="1" />
|
||||
<key id="two" output="2" />
|
||||
<key id="three" output="3" />
|
||||
</keys>
|
||||
<layers formId="iso">
|
||||
<layer id="base">
|
||||
<row keys="one two three" />
|
||||
</layer>
|
||||
</layers>
|
||||
<layers formId="touch" minDeviceWidth="x">
|
||||
<layer id="base">
|
||||
<row keys="one" />
|
||||
<row keys="two" />
|
||||
<row keys="three" />
|
||||
</layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
29
developer/src/kmc-ldml/test/fixtures/sections/layr/error-dup-width.xml
vendored
Normal file
29
developer/src/kmc-ldml/test/fixtures/sections/layr/error-dup-width.xml
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<keyboard3 xmlns="https://schemas.unicode.org/cldr/45/keyboard3" locale="und" conformsTo="45">
|
||||
<info name="layr-error-custom-form"/>
|
||||
<keys>
|
||||
<key id="one" output="1" />
|
||||
<key id="two" output="2" />
|
||||
<key id="three" output="3" />
|
||||
</keys>
|
||||
<layers formId="iso">
|
||||
<layer id="base">
|
||||
<row keys="one two three" />
|
||||
</layer>
|
||||
</layers>
|
||||
<layers formId="touch" minDeviceWidth="120">
|
||||
<layer id="base">
|
||||
<row keys="one" />
|
||||
<row keys="two" />
|
||||
<row keys="three" />
|
||||
</layer>
|
||||
</layers>
|
||||
<layers formId="touch" minDeviceWidth="120"> <!-- dup -->
|
||||
<layer id="base">
|
||||
<row keys="one" />
|
||||
<row keys="two" />
|
||||
<row keys="three" />
|
||||
</layer>
|
||||
</layers>
|
||||
</keyboard3>
|
||||
|
|
@ -5,4 +5,5 @@
|
|||
|
||||
<keys />
|
||||
<!-- invalid: need a layer! -->
|
||||
<!-- this keyboard doesn't even have a <layers/> element - no offset will be generated -->
|
||||
</keyboard3>
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ export async function compileKeyboard(inputFilename: string, options: LdmlCompil
|
|||
|
||||
const valid = await k.validate(source);
|
||||
if (validateMessages) {
|
||||
assert.sameDeepMembers(compilerTestCallbacks.messages, validateMessages, "validation messages mismatch");
|
||||
assert.sameDeepMembers(scrubContextFromMessages(compilerTestCallbacks.messages), validateMessages, "validation messages mismatch");
|
||||
assert.notEqual(valid, expectFailValidate, 'validation failure');
|
||||
} else {
|
||||
checkMessages();
|
||||
|
|
@ -155,7 +155,7 @@ export async function compileKeyboard(inputFilename: string, options: LdmlCompil
|
|||
|
||||
const kmx = await k.compile(source);
|
||||
if (compileMessages) {
|
||||
assert.sameDeepMembers(compilerTestCallbacks.messages, compileMessages, "compiler messages mismatch");
|
||||
assert.sameDeepMembers(scrubContextFromMessages(compilerTestCallbacks.messages), compileMessages, "compiler messages mismatch");
|
||||
} else {
|
||||
checkMessages();
|
||||
}
|
||||
|
|
@ -259,6 +259,26 @@ export interface CompilationCase {
|
|||
retainOffsetInMessages?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrub 'context' from messages. to simplify unit tests
|
||||
* @param messages input array of messages
|
||||
* @returns copy of messages
|
||||
*/
|
||||
export function scrubContextFromMessages(messages: CompilerEvent[]): CompilerEvent[] {
|
||||
return messages.map(m => {
|
||||
const scrubbed = Object.assign({}, m);
|
||||
// Turn this on once all messages have offsets, see messages.tests.ts
|
||||
// if (!scrubbed.offset) {
|
||||
// throw Error(`Error, no offset detected in message ${CompilerError.formatEvent(m)}`);
|
||||
// }
|
||||
delete scrubbed.offset;
|
||||
delete scrubbed.line;
|
||||
delete scrubbed.filename;
|
||||
delete scrubbed.column;
|
||||
return scrubbed;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a bunch of cases
|
||||
* @param cases cases to run
|
||||
|
|
@ -283,15 +303,7 @@ export function testCompilationCases(compiler: SectionCompilerNew, cases : Compi
|
|||
let messagesToCheck = callbacks.messages;
|
||||
// scrub offsets from messages to reduce churn in the test casws
|
||||
if (!testcase.retainOffsetInMessages && callbacks.messages) {
|
||||
messagesToCheck = callbacks.messages.map(m => {
|
||||
const scrubbed = Object.assign({}, m);
|
||||
// Turn this on once all messages have offsets, see messages.tests.ts
|
||||
// if (!scrubbed.offset) {
|
||||
// throw Error(`Error, no offset detected in message ${CompilerError.formatEvent(m)}`);
|
||||
// }
|
||||
delete scrubbed.offset;
|
||||
return scrubbed;
|
||||
});
|
||||
messagesToCheck = scrubContextFromMessages(callbacks.messages);
|
||||
}
|
||||
const testcaseErrors = matchCompilerEventsOrBoolean(messagesToCheck, testcase.errors);
|
||||
const testcaseWarnings = matchCompilerEvents(messagesToCheck, testcase.warnings);
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ describe('keys.kmap', function () {
|
|||
{
|
||||
subpath: 'sections/keys/invalid-bad-modifier.xml',
|
||||
errors: [
|
||||
LdmlCompilerMessages.Error_InvalidModifier({id:'base',modifiers:'altR-shift'}),
|
||||
LdmlCompilerMessages.Error_InvalidModifier({ modifiers: 'altR-shift' }),
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import 'mocha';
|
|||
import { assert } from 'chai';
|
||||
import { LayrCompiler } from '../src/compiler/layr.js';
|
||||
import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js';
|
||||
import { compilerTestCallbacks, testCompilationCases } from './helpers/index.js';
|
||||
import { compilerTestCallbacks, testCompilationCases, withOffset } from './helpers/index.js';
|
||||
import { KMXPlus } from '@keymanapp/common-types';
|
||||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
|
||||
|
|
@ -96,8 +96,7 @@ describe('layr', function () {
|
|||
subpath: 'sections/keys/invalid-bad-modifier.xml',
|
||||
errors: [
|
||||
LdmlCompilerMessages.Error_InvalidModifier({
|
||||
id: 'base',
|
||||
modifiers: 'altR-shift'
|
||||
modifiers: 'altR-shift',
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
|
@ -108,7 +107,14 @@ describe('layr', function () {
|
|||
{
|
||||
// missing layer element
|
||||
subpath: 'sections/layr/invalid-missing-layer.xml',
|
||||
errors: [LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement(withOffset(258))],
|
||||
retainOffsetInMessages: true,
|
||||
},
|
||||
{
|
||||
// missing layer element
|
||||
subpath: 'sections/layr/invalid-missing-layer2.xml',
|
||||
errors: [LdmlCompilerMessages.Error_MustBeAtLeastOneLayerElement()],
|
||||
retainOffsetInMessages: true,
|
||||
},
|
||||
{
|
||||
// keep in sync with similar test in test-keys.ts
|
||||
|
|
@ -132,7 +138,7 @@ describe('layr', function () {
|
|||
{
|
||||
subpath: 'sections/layr/error-bogus-modifiers.xml',
|
||||
errors: [
|
||||
LdmlCompilerMessages.Error_InvalidModifier({ id: '', modifiers: 'caps bogus'}),
|
||||
LdmlCompilerMessages.Error_InvalidModifier({ modifiers: 'caps bogus' }),
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -160,5 +166,18 @@ describe('layr', function () {
|
|||
}
|
||||
},
|
||||
},
|
||||
{
|
||||
subpath: 'sections/layr/error-dup-width.xml',
|
||||
errors: [
|
||||
LdmlCompilerMessages.Error_DuplicateLayerWidth({ minDeviceWidth: 120}),
|
||||
]
|
||||
},
|
||||
...[0, 1024, 1500, `x` as unknown as number].map(minDeviceWidth => ({
|
||||
subpath: `sections/layr/error-bad-width-${minDeviceWidth}.xml`,
|
||||
errors: [
|
||||
//
|
||||
LdmlCompilerMessages.Error_InvalidLayerWidth({ minDeviceWidth }),
|
||||
]
|
||||
})),
|
||||
]);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import 'mocha';
|
||||
import { assert } from 'chai';
|
||||
import { LocaCompiler } from '../src/compiler/loca.js';
|
||||
import { compilerTestCallbacks, loadSectionFixture } from './helpers/index.js';
|
||||
import { compilerTestCallbacks, loadSectionFixture, withOffset } from './helpers/index.js';
|
||||
import { KMXPlus } from '@keymanapp/common-types';
|
||||
import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js';
|
||||
|
||||
|
|
@ -26,10 +26,10 @@ describe('loca', function () {
|
|||
|
||||
// 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], LdmlCompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-FR', locale: 'fr'}));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[1], LdmlCompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'km-khmr-kh', locale: 'km'}));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[2], LdmlCompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-fr', locale: 'fr'}));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[3], LdmlCompilerMessages.Hint_OneOrMoreRepeatedLocales());
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-FR', locale: 'fr'}, withOffset(136)));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[1], LdmlCompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'km-khmr-kh', locale: 'km'}, withOffset(136)));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[2], LdmlCompilerMessages.Hint_LocaleIsNotMinimalAndClean({sourceLocale: 'fr-fr', locale: 'fr'}, withOffset(136)));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[3], LdmlCompilerMessages.Hint_OneOrMoreRepeatedLocales(withOffset(136)));
|
||||
|
||||
// Original is 6 locales, now five minimized in the results
|
||||
assert.equal(loca.locales.length, 5);
|
||||
|
|
@ -46,6 +46,6 @@ describe('loca', function () {
|
|||
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], LdmlCompilerMessages.Error_InvalidLocale({tag:'en-*'}));
|
||||
assert.deepEqual(compilerTestCallbacks.messages[0], LdmlCompilerMessages.Error_InvalidLocale({tag:'en-*'}, withOffset(40)));
|
||||
})
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,18 +15,20 @@ describe('LdmlCompilerMessages', function () {
|
|||
/** all fns */
|
||||
let total = 0;
|
||||
/** does not take line numbers */
|
||||
let noLines = 0;
|
||||
const noLines = new Set<string>();
|
||||
/** takes line numbers */
|
||||
let lines = 0;
|
||||
const fakeOffsetNumber = 1234;
|
||||
const fakeOffsetObject = withOffset(fakeOffsetNumber);
|
||||
for(const key of keys) {
|
||||
if(typeof m[key] == 'function') {
|
||||
// exclude this one, does not need line numbers
|
||||
if (key == 'Error_InvalidFile') continue;
|
||||
if (typeof m[key] == 'function') {
|
||||
total++;
|
||||
const f = m[key] as Function;
|
||||
// console.log(`${f.name}: ${f.length}`);
|
||||
if (f.length === 0) { // Error_foo()
|
||||
noLines++;
|
||||
noLines.add(key);
|
||||
continue;
|
||||
}
|
||||
// now try to call it
|
||||
|
|
@ -42,16 +44,30 @@ describe('LdmlCompilerMessages', function () {
|
|||
expect(resp.offset).to.equal(fakeOffsetNumber, `Offset number round trip for error ${f.name} did not work, check the message function`);
|
||||
} else {
|
||||
// did not get a column number back
|
||||
noLines++;
|
||||
noLines.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(lines).to.not.be.equal(0, `None of ${total} messages had offset reporting.`);
|
||||
if (noLines > 0) {
|
||||
// Once this goes to zero, make it an error if it goes up!
|
||||
// Oh, and while you're here, once this is zero, uncomment the code in testCompilationCases
|
||||
// that asserts that all messages are actually generated with an offset.
|
||||
console.warn(`TODO-LDML (#10622) ${noLines}/${total} messages did not have detectable offset (line number) reporting.`);
|
||||
expect(Array.from(noLines.values())).to.deep.equal([], `${noLines.size}/${total} ${Number((noLines.size/total)*100).toFixed(0)}% of message(s) did not have detectable offset (line number) reporting`);
|
||||
});
|
||||
it('Should only have simple ${def(o...)} interpolation', () => {
|
||||
const m = LdmlCompilerMessages as Record<string,any>;
|
||||
const keys = Object.keys(LdmlCompilerMessages);
|
||||
const complexInterpolation : string[] = [];
|
||||
for(const key of keys) {
|
||||
// exclude this one, does not need line numbers
|
||||
if (key == 'Error_InvalidFile') continue;
|
||||
if (typeof m[key] == 'function') {
|
||||
const f = m[key] as Function;
|
||||
const s = f.toString();
|
||||
// scan for interpolation
|
||||
// Interpolation.. A non-escaped ${ that's NOT followed by `def(o.` triggers this error.
|
||||
if (/.*`.*(?<!\\)(?:\\\\)*\$\{(?!def\(o\.)/.test(s)) {
|
||||
complexInterpolation.push(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(complexInterpolation).to.deep.equal([], 'Complex interpolation found, please only use the ${def(o... form')
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'mocha';
|
|||
import { assert } from 'chai';
|
||||
import { compileKeyboard, compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js';
|
||||
import { LdmlCompilerMessages } from '../src/compiler/ldml-compiler-messages.js';
|
||||
import { util } from '@keymanapp/common-types';
|
||||
|
||||
|
||||
/** strs tests */
|
||||
|
|
@ -17,8 +18,8 @@ describe('strs', function () {
|
|||
const kmx = await compileKeyboard(inputFilename, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false },
|
||||
[
|
||||
// validation messages
|
||||
LdmlCompilerMessages.Error_IllegalCharacters({ count: 5, lowestCh: 0xFDD0 }),
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
LdmlCompilerMessages.Error_IllegalCharacters({ count: 5, lowestCh: util.describeCodepoint(0xFDD0) }),
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: util.describeCodepoint(0xE010) }),
|
||||
],
|
||||
true, // validation should fail
|
||||
[
|
||||
|
|
@ -32,12 +33,12 @@ describe('strs', function () {
|
|||
const kmx = await compileKeyboard(inputFilename, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false },
|
||||
[
|
||||
// validation messages
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: util.describeCodepoint(0xE010) }),
|
||||
],
|
||||
false, // validation should pass
|
||||
[
|
||||
// same messages
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: util.describeCodepoint(0xE010) }),
|
||||
]);
|
||||
assert.isNotNull(kmx);
|
||||
});
|
||||
|
|
@ -47,14 +48,14 @@ describe('strs', function () {
|
|||
const kmx = await compileKeyboard(inputFilename, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false },
|
||||
[
|
||||
// validation messages
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
LdmlCompilerMessages.Warn_UnassignedCharacters({ count: 1, lowestCh: 0x0CFFFD }),
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: util.describeCodepoint(0xE010) }),
|
||||
LdmlCompilerMessages.Warn_UnassignedCharacters({ count: 1, lowestCh: util.describeCodepoint(0x0CFFFD) }),
|
||||
],
|
||||
false, // validation should pass
|
||||
[
|
||||
// same messages
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: 0xE010 }),
|
||||
LdmlCompilerMessages.Warn_UnassignedCharacters({ count: 1, lowestCh: 0x0CFFFD }),
|
||||
LdmlCompilerMessages.Hint_PUACharacters({ count: 2, lowestCh: util.describeCodepoint(0xE010) }),
|
||||
LdmlCompilerMessages.Warn_UnassignedCharacters({ count: 1, lowestCh: util.describeCodepoint(0x0CFFFD) }),
|
||||
]);
|
||||
assert.isNotNull(kmx);
|
||||
});
|
||||
|
|
@ -63,7 +64,8 @@ describe('strs', function () {
|
|||
const inputFilename = makePathToFixture('sections/tran/fail-bad-tran-2.xml');
|
||||
const kmx = await compileKeyboard(inputFilename, { ...compilerTestOptions, saveDebug: true, shouldAddCompilerVersion: false },
|
||||
[
|
||||
LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: 295 }),
|
||||
// \uXXXX is not allowed in this context
|
||||
LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: "\\u0127", recommended: "\\u{127}" }),
|
||||
],
|
||||
true, // validation should fail
|
||||
[
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ describe('tran', function () {
|
|||
{
|
||||
subpath: 'sections/tran/fail-duplicate-type.xml',
|
||||
errors: [
|
||||
LdmlCompilerMessages.Error_DuplicateTransformsType({types: ['simple']})
|
||||
LdmlCompilerMessages.Error_DuplicateTransformsType({type: 'simple'})
|
||||
]
|
||||
},
|
||||
{
|
||||
|
|
@ -273,13 +273,14 @@ describe('tran', function () {
|
|||
{
|
||||
subpath: 'sections/tran/fail-bad-reorder-2.xml',
|
||||
errors: [
|
||||
LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: 0x1a6b }),
|
||||
// 'cp' has exact casing from original
|
||||
LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: "\\u1A6B", recommended: "\\u{1a6b}" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
subpath: 'sections/tran/fail-bad-reorder-3.xml',
|
||||
errors: [
|
||||
LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: 0x1a60 }),
|
||||
LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: "\\u1A60", recommended: "\\u{1a60}" }),
|
||||
],
|
||||
},
|
||||
// error due to bad regex
|
||||
|
|
@ -295,7 +296,7 @@ describe('tran', function () {
|
|||
// also used in test-compiler-e2e.ts
|
||||
subpath: `sections/tran/fail-bad-tran-2.xml`,
|
||||
errors: [
|
||||
LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: 295 }),
|
||||
LdmlCompilerMessages.Error_InvalidQuadEscape({ cp: "\\u0127", recommended: "\\u{127}" }),
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
@ -466,7 +467,7 @@ describe('bksp', function () {
|
|||
'doesnt_exist_1',
|
||||
'doesnt_exist_2',
|
||||
'doesnt_exist_3',
|
||||
]
|
||||
].join(',')
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -238,7 +238,7 @@ describe('vars', function () {
|
|||
'doesnt_exist_1',
|
||||
'doesnt_exist_2',
|
||||
'doesnt_exist_3',
|
||||
]
|
||||
].join(',')
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
"url": "https://github.com/keymanapp/keyman/issues"
|
||||
},
|
||||
"homepage": "https://keyman.com/",
|
||||
"scripts": {},
|
||||
"dependencies": {
|
||||
"monaco-editor": "0.15.6"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/build/zip.inc.sh"
|
||||
|
||||
# This script runs from its parent's folder
|
||||
cd "$(dirname "$THIS_SCRIPT")/.."
|
||||
|
||||
|
|
@ -68,16 +70,17 @@ FRAMEWORK="KeymanEngine.xcframework"
|
|||
KEYMAN_SAMPLES="samples"
|
||||
|
||||
echo "engine dest: $KMEI_DST"
|
||||
ZIP_FLAGS=("-q" "-r" "-X") # quiet, recursive, no-extra
|
||||
|
||||
echo "Zipping ${FRAMEWORK} => ${UPLOAD_DIR}/${KMEI_DST}..."
|
||||
cd "${KMEI_FRAMEWORK_BASE}"
|
||||
zip -qrX "${KMEI_DST}" ${FRAMEWORK}
|
||||
add_zip_files "${KMEI_DST}" "${ZIP_FLAGS[@]}" "${FRAMEWORK}"
|
||||
cd "$WORK_DIR"
|
||||
|
||||
echo "Copying Keyman Engine samples into ${UPLOAD_DIR}/${KMEI_DST_NAME}..."
|
||||
cp -rf "${KEYMAN_SAMPLES}" "${UPLOAD_DIR}/samples"
|
||||
cd "${UPLOAD_DIR}"
|
||||
zip -qr "${KMEI_DST_NAME}" "samples"
|
||||
add_zip_files "${KMEI_DST_NAME}" "-x@../../zip-excludes" "${ZIP_FLAGS[@]}" "samples"
|
||||
rm -rf "samples"
|
||||
cd "$WORK_DIR"
|
||||
|
||||
|
|
@ -97,7 +100,7 @@ KEYMANAPP_SIM_APP_DST="keyman-ios-simulator-${BUILD_NUMBER}.app.zip"
|
|||
|
||||
echo "Zipping Keyman simulator artifact ${KEYMANAPP_SIM_APP} => ${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}..."
|
||||
cd "${KEYMANAPP_SIM_FOLDER}"
|
||||
zip -qrX "${WORK_DIR}/${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}" "Keyman.app"
|
||||
add_zip_files "${WORK_DIR}/${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}" "${ZIP_FLAGS[@]}" "Keyman.app"
|
||||
echo "${WORK_DIR}/${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}"
|
||||
cd "$WORK_DIR"
|
||||
|
||||
|
|
@ -118,7 +121,7 @@ if [ "${RELEASE_OEM_FIRSTVOICES}" = true ]; then
|
|||
|
||||
echo "Zipping FirstVoices simulator artifact ${FIRSTVOICESAPP_SIM_APP} => ${UPLOAD_DIR}/${KEYMANAPP_SIM_APP_DST}..."
|
||||
cd "${FIRSTVOICESAPP_SIM_FOLDER}"
|
||||
zip -qrX "${WORK_DIR}/${UPLOAD_DIR}/${FIRSTVOICESAPP_SIM_APP_DST}" "FirstVoices.app"
|
||||
add_zip_files "${WORK_DIR}/${UPLOAD_DIR}/${FIRSTVOICESAPP_SIM_APP_DST}" "${ZIP_FLAGS[@]}" "FirstVoices.app"
|
||||
cd "$WORK_DIR"
|
||||
fi
|
||||
|
||||
|
|
|
|||
2
ios/zip-excludes
Normal file
2
ios/zip-excludes
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
samples/*/build.sh
|
||||
|
||||
|
|
@ -15,12 +15,14 @@ sudo apt-get upgrade
|
|||
sudo apt-get install keyman onboard-keyman
|
||||
```
|
||||
|
||||
An older version of Keyman for Linux packages is also available in any current Debian at:
|
||||
A possibly older version of Keyman for Linux packages is also available in
|
||||
any current Debian at:
|
||||
[https://packages.debian.org/keyman](https://packages.debian.org/keyman)
|
||||
|
||||
**Note** in order to fully work you'll need a patched version of ibus
|
||||
which is available in the above launchpad repo. Patched versions of ibus
|
||||
will have the string `sil` in the package version number.
|
||||
**Note** Versions before Keyman 18 required a patched version of ibus in order
|
||||
to fully work. Patched versions of ibus were available in the above mentioned
|
||||
launchpad repository and had the string `sil` in the package version number.
|
||||
Keyman 18+ no longer requires a patched version of ibus.
|
||||
|
||||
## Q. How do I install a Keyman keyboard?
|
||||
|
||||
|
|
|
|||
|
|
@ -62,14 +62,14 @@ build_action() {
|
|||
builder_echo "Create version.py"
|
||||
pushd keyman_config
|
||||
sed \
|
||||
-e "s/_KEYMAN_VERSION_/${KEYMAN_VERSION}/g" \
|
||||
-e "s/_KEYMAN_VERSION_WITH_TAG_/${KEYMAN_VERSION_WITH_TAG}/g" \
|
||||
-e "s/_KEYMAN_VERSION_GIT_TAG_/${KEYMAN_VERSION_GIT_TAG}/g" \
|
||||
-e "s/_KEYMAN_VERSION_MAJOR_/${KEYMAN_VERSION_MAJOR}/g" \
|
||||
-e "s/_KEYMAN_VERSION_RELEASE_/${KEYMAN_VERSION_RELEASE}/g" \
|
||||
-e "s/_KEYMAN_TIER_/${KEYMAN_TIER}/g" \
|
||||
-e "s/_KEYMAN_VERSION_ENVIRONMENT_/${KEYMAN_VERSION_ENVIRONMENT}/g" \
|
||||
-e "s/_UPLOAD_SENTRY_/${UPLOAD_SENTRY}/g" \
|
||||
-e "s/__KEYMAN_VERSION__/${KEYMAN_VERSION}/g" \
|
||||
-e "s/__KEYMAN_VERSION_WITH_TAG__/${KEYMAN_VERSION_WITH_TAG}/g" \
|
||||
-e "s/__KEYMAN_VERSION_GIT_TAG__/${KEYMAN_VERSION_GIT_TAG}/g" \
|
||||
-e "s/__KEYMAN_VERSION_MAJOR__/${KEYMAN_VERSION_MAJOR}/g" \
|
||||
-e "s/__KEYMAN_VERSION_RELEASE__/${KEYMAN_VERSION_RELEASE}/g" \
|
||||
-e "s/__KEYMAN_TIER__/${KEYMAN_TIER}/g" \
|
||||
-e "s/__KEYMAN_VERSION_ENVIRONMENT__/${KEYMAN_VERSION_ENVIRONMENT}/g" \
|
||||
-e "s/__UPLOAD_SENTRY__/${UPLOAD_SENTRY}/g" \
|
||||
version.py.in > version.py
|
||||
popd
|
||||
pushd buildtools
|
||||
|
|
|
|||
|
|
@ -4,12 +4,12 @@
|
|||
# 1) we don't load dependencies by storing it in __init__.py
|
||||
# 2) we can import it in setup.py for the same reason
|
||||
# 3) we can import it into your module module
|
||||
__version__ = "_KEYMAN_VERSION_"
|
||||
__versionwithtag__ = "_KEYMAN_VERSION_WITH_TAG_"
|
||||
__majorversion__ = "_KEYMAN_VERSION_MAJOR_"
|
||||
__releaseversion__ = "_KEYMAN_VERSION_RELEASE_"
|
||||
__tier__ = "_KEYMAN_TIER_"
|
||||
__version__ = "__KEYMAN_VERSION__"
|
||||
__versionwithtag__ = "__KEYMAN_VERSION_WITH_TAG__"
|
||||
__majorversion__ = "__KEYMAN_VERSION_MAJOR__"
|
||||
__releaseversion__ = "__KEYMAN_VERSION_RELEASE__"
|
||||
__tier__ = "__KEYMAN_TIER__"
|
||||
__pkgversion__ = "(local)"
|
||||
__environment__ = "_KEYMAN_VERSION_ENVIRONMENT_"
|
||||
__uploadsentry__ = "_UPLOAD_SENTRY_"
|
||||
__versiongittag__ = "_KEYMAN_VERSION_GIT_TAG_"
|
||||
__environment__ = "__KEYMAN_VERSION_ENVIRONMENT__"
|
||||
__uploadsentry__ = "__UPLOAD_SENTRY__"
|
||||
__versiongittag__ = "__KEYMAN_VERSION_GIT_TAG__"
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#!/bin/bash
|
||||
PYTHONPATH=.:${PYTHONPATH}
|
||||
export PYTHONPATH=.:${PYTHONPATH}
|
||||
|
||||
XDG_CONFIG_HOME=$(mktemp --directory)
|
||||
export XDG_CONFIG_HOME
|
||||
|
|
@ -13,6 +13,8 @@ if [[ "$1" == "--coverage" ]]; then
|
|||
fi
|
||||
|
||||
if [[ -n "${TEAMCITY_GIT_PATH}" ]]; then
|
||||
PYTHONPATH=$(dirname "$0")/../tools:${PYTHONPATH}
|
||||
|
||||
if ! pip3 list --format=columns | grep -q teamcity-messages; then
|
||||
if [[ -n "${TEAMCITY_PLATFORM}" ]] || [[ -n "${DOCKER_RUNNING}" ]]; then
|
||||
# Ubuntu 24.04+ prevents mixing pip and system packages and wants us
|
||||
|
|
@ -29,7 +31,9 @@ if [[ -n "${TEAMCITY_GIT_PATH}" ]]; then
|
|||
# shellcheck disable=SC2086
|
||||
pip3 install --user ${PIP_ARGS:-} teamcity-messages
|
||||
fi
|
||||
test_module=teamcity.unittestpy
|
||||
test_module=teamcity_pytestrunner.unittestpy
|
||||
echo "##teamcity[testStarted name='|[keyman-config|] Running unit tests']"
|
||||
echo "##teamcity[flowStarted flowId='unit_tests']"
|
||||
else
|
||||
test_module=unittest
|
||||
extra_opts=-v
|
||||
|
|
@ -38,4 +42,8 @@ fi
|
|||
# shellcheck disable=SC2086
|
||||
python3 ${coverage:-} -m "${test_module:-}" discover ${extra_opts:-} -s tests/ -p "*_tests.py"
|
||||
|
||||
if [[ -n "${TEAMCITY_GIT_PATH}" ]]; then
|
||||
echo "##teamcity[flowFinished flowId='unit_tests']"
|
||||
echo "##teamcity[testFinished name='|[keyman-config|] Finished running unit tests']"
|
||||
fi
|
||||
rm -rf "${XDG_CONFIG_HOME}"
|
||||
|
|
|
|||
|
|
@ -78,5 +78,5 @@ function checkAndInstallRequirements()
|
|||
sudo mk-build-deps debian/control
|
||||
wait_for_apt_deb && sudo DEBIAN_FRONTEND="noninteractive" \
|
||||
apt-get -qy --allow-downgrades install ./keyman-build-deps_*.deb
|
||||
sudo rm -f keyman-build-deps_*
|
||||
rm -f keyman-build-deps_*
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,14 +32,14 @@ quilt new version_py.diff
|
|||
quilt add "version.py"
|
||||
|
||||
sed \
|
||||
-e "s/_KEYMAN_VERSION_/${KEYMAN_VERSION}/g" \
|
||||
-e "s/_KEYMAN_VERSION_WITH_TAG_/${KEYMAN_VERSION_WITH_TAG}/g" \
|
||||
-e "s/_KEYMAN_VERSION_GIT_TAG_/${KEYMAN_VERSION_GIT_TAG}/g" \
|
||||
-e "s/_KEYMAN_VERSION_MAJOR_/${KEYMAN_VERSION_MAJOR}/g" \
|
||||
-e "s/_KEYMAN_VERSION_RELEASE_/${KEYMAN_VERSION_RELEASE}/g" \
|
||||
-e "s/_KEYMAN_TIER_/${KEYMAN_TIER}/g" \
|
||||
-e "s/_KEYMAN_VERSION_ENVIRONMENT_/${KEYMAN_VERSION_ENVIRONMENT}/g" \
|
||||
-e "s/_UPLOAD_SENTRY_/${UPLOAD_SENTRY}/g" \
|
||||
-e "s/__KEYMAN_VERSION__/${KEYMAN_VERSION}/g" \
|
||||
-e "s/__KEYMAN_VERSION_WITH_TAG__/${KEYMAN_VERSION_WITH_TAG}/g" \
|
||||
-e "s/__KEYMAN_VERSION_GIT_TAG__/${KEYMAN_VERSION_GIT_TAG}/g" \
|
||||
-e "s/__KEYMAN_VERSION_MAJOR__/${KEYMAN_VERSION_MAJOR}/g" \
|
||||
-e "s/__KEYMAN_VERSION_RELEASE__/${KEYMAN_VERSION_RELEASE}/g" \
|
||||
-e "s/__KEYMAN_TIER__/${KEYMAN_TIER}/g" \
|
||||
-e "s/__KEYMAN_VERSION_ENVIRONMENT__/${KEYMAN_VERSION_ENVIRONMENT}/g" \
|
||||
-e "s/__UPLOAD_SENTRY__/${UPLOAD_SENTRY}/g" \
|
||||
version.py.in > version.py
|
||||
quilt refresh
|
||||
quilt pop -a
|
||||
|
|
|
|||
12
linux/tools/teamcity_pytestrunner/README.md
Normal file
12
linux/tools/teamcity_pytestrunner/README.md
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
# Teamcity Python Test Runner
|
||||
|
||||
This implements an improved version of JetBrains' python unittest runner for
|
||||
Teamcity. It makes the output of the tests indented and collapsible under
|
||||
the test block by specifying a parent flow.
|
||||
|
||||
Requires the `teamcity-messages` package.
|
||||
|
||||
## References
|
||||
|
||||
- <https://www.jetbrains.com/help/teamcity/service-messages.html#Message+FlowId>
|
||||
- <https://github.com/JetBrains/teamcity-messages>
|
||||
61
linux/tools/teamcity_pytestrunner/unittestpy.py
Normal file
61
linux/tools/teamcity_pytestrunner/unittestpy.py
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import sys
|
||||
from teamcity.messages import TeamcityServiceMessages
|
||||
from teamcity.unittestpy import TeamcityTestResult, TeamcityTestRunner
|
||||
from unittest import main
|
||||
|
||||
|
||||
class KeymanTeamcityServiceMessages(TeamcityServiceMessages):
|
||||
def __init__(self, parent_flow='unit_tests'):
|
||||
self.parent_flow = parent_flow
|
||||
super().__init__()
|
||||
|
||||
def testStarted(self, testName, captureStandardOutput=None, flowId=None, metainfo=None):
|
||||
self.message('testStarted', name=testName, metainfo=metainfo)
|
||||
self.message('flowStarted', flowId=flowId, parent=self.parent_flow)
|
||||
|
||||
def testFinished(self, testName, testDuration=None, flowId=None):
|
||||
self.message('flowFinished', flowId=flowId, parent=self.parent_flow)
|
||||
if testDuration is not None:
|
||||
duration_ms = testDuration.days * 86400000 + \
|
||||
testDuration.seconds * 1000 + \
|
||||
int(testDuration.microseconds / 1000)
|
||||
self.message('testFinished', name=testName, duration=str(duration_ms))
|
||||
else:
|
||||
self.message('testFinished', name=testName)
|
||||
|
||||
def testIgnored(self, testName, message='', flowId=None):
|
||||
self.message('flowFinished', flowId=flowId, parent=self.parent_flow)
|
||||
self.message('testIgnored', name=testName, message=message)
|
||||
|
||||
def testFailed(self, testName, message='', details='', flowId=None, comparison_failure=None):
|
||||
self.message('flowFinished', flowId=flowId, parent=self.parent_flow)
|
||||
if not comparison_failure:
|
||||
self.message('testFailed', name=testName, message=message, details=details)
|
||||
else:
|
||||
diff_message = u"\n{0} != {1}\n".format(comparison_failure.actual, comparison_failure.expected)
|
||||
self.message(
|
||||
'testFailed',
|
||||
name=testName,
|
||||
message=str(message) + diff_message,
|
||||
details=details,
|
||||
type="comparisonFailure",
|
||||
actual=comparison_failure.actual,
|
||||
expected=comparison_failure.expected,
|
||||
)
|
||||
|
||||
|
||||
class KeymanTestResult(TeamcityTestResult):
|
||||
def __init__(self, stream=sys.stdout, descriptions=None, verbosity=None):
|
||||
super().__init__(stream, descriptions, verbosity)
|
||||
self.messages = KeymanTeamcityServiceMessages()
|
||||
|
||||
|
||||
class KeymanTestRunner(TeamcityTestRunner):
|
||||
resultclass = KeymanTestResult
|
||||
|
||||
def run(self, test):
|
||||
return super().run(test)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(module=None, testRunner=KeymanTestRunner())
|
||||
|
|
@ -69,16 +69,12 @@ PREPRELEASE=false
|
|||
UPDATE_VERSION_IN_PLIST=true
|
||||
DO_CODESIGN=true
|
||||
CODESIGNING_SUPPRESSION="CODE_SIGN_IDENTITY=\"\" CODE_SIGNING_REQUIRED=NO"
|
||||
|
||||
QUIET=true
|
||||
QUIET=false
|
||||
|
||||
if builder_verbose; then
|
||||
BUILD_OPTIONS=""
|
||||
QUIET_FLAG=
|
||||
QUIET=false
|
||||
else
|
||||
BUILD_OPTIONS="-quiet"
|
||||
QUIET_FLAG="-quiet"
|
||||
fi
|
||||
|
||||
BUILD_ACTIONS="build"
|
||||
|
|
@ -290,7 +286,7 @@ do_publish() {
|
|||
builder_heading "Preparing files for release deployment..."
|
||||
./setup/build.sh
|
||||
|
||||
"$KM4MIM_BASE_PATH/make-km-dmg.sh" $QUIET_FLAG
|
||||
"$KM4MIM_BASE_PATH/make-km-dmg.sh"
|
||||
|
||||
# Create download info
|
||||
"$KM4MIM_BASE_PATH/write-download_info.sh"
|
||||
|
|
|
|||
6
package-lock.json
generated
6
package-lock.json
generated
|
|
@ -12738,9 +12738,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/tar-fs": {
|
||||
"version": "3.0.8",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.8.tgz",
|
||||
"integrity": "sha512-ZoROL70jptorGAlgAYiLoBLItEKw/fUxg9BSYK/dF/GAGYFJOJJJMvjPAKDJraCXFwadD456FCuvLWgfhMsPwg==",
|
||||
"version": "3.0.9",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.0.9.tgz",
|
||||
"integrity": "sha512-XF4w9Xp+ZQgifKakjZYmFdkLoSWd34VGKcsTCwlNWM7QG3ZbaxnTsaBwnjFZqHRf/rROxaR8rXnbtwdvaDI+lA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
|
|
|||
|
|
@ -41,8 +41,10 @@ type -t nvm >/dev/null || {
|
|||
}
|
||||
}
|
||||
|
||||
nvm use "$REQUIRED_NODE_VERSION" || \
|
||||
(nvm install "$REQUIRED_NODE_VERSION" && nvm use "$REQUIRED_NODE_VERSION")
|
||||
if ! nvm use "${REQUIRED_NODE_VERSION}"; then
|
||||
nvm install "${REQUIRED_NODE_VERSION}"
|
||||
nvm use "${REQUIRED_NODE_VERSION}"
|
||||
fi
|
||||
|
||||
# Beware the hardcoded path below -- it should already be in the system PATH
|
||||
|
||||
|
|
|
|||
|
|
@ -166,8 +166,9 @@ while IFS= read -r line; do
|
|||
# Which platform are we watching?
|
||||
eval watch='$'watch_$platform
|
||||
# Add common patterns to the watch list
|
||||
watch="^($platform|(oem/[^/]+/$platform)|resources|$watch)"
|
||||
if [[ "$line" =~ $watch ]]; then
|
||||
watch="^(${platform}|(oem/[^/]+/${platform})|resources/((?!teamcity)|teamcity/(${platform}|includes))|$watch)"
|
||||
# Since bash doesn't support negative look-aheads we use grep to test
|
||||
if echo "${line}" | grep --quiet --perl-regexp "${watch}"; then
|
||||
build_platforms+=($platform)
|
||||
fi
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ bc_test_common_linux=(Keyman_Test_Common_Linux)
|
|||
|
||||
vcs_test=HttpsGithubComKeymanappKeymanPRs
|
||||
|
||||
# Master (Alpha) Build Configurations; these diverge when we need new build configurations for new versions
|
||||
# Master (Alpha) Build Configurations; these may diverge when we need new build configurations for new versions
|
||||
|
||||
bc_master_android=(KeymanAndroid_Build)
|
||||
bc_master_ios=(Keyman_iOS_Master)
|
||||
|
|
@ -86,57 +86,7 @@ bc_beta_developer=(Keyman_Developer_Release)
|
|||
|
||||
vcs_beta=HttpsGithubComKeymanappKeyman
|
||||
|
||||
# Stable 14.0 Build Configurations
|
||||
|
||||
bc_stable_14_0_android=(KeymanAndroid_Build)
|
||||
bc_stable_14_0_ios=(Keyman_iOS_Master)
|
||||
bc_stable_14_0_linux=(KeymanLinux_Master deb-release-packaging_GitHub)
|
||||
bc_stable_14_0_mac=(KeymanMac_Master)
|
||||
bc_stable_14_0_windows=(Keyman_Build)
|
||||
bc_stable_14_0_web=(Keymanweb_Build)
|
||||
|
||||
# 16.0+ Keyman Developer is split from Windows, but TC configuration will not fail
|
||||
# on older versions, just no-op
|
||||
bc_stable_14_0_developer=(Keyman_Developer_Release)
|
||||
|
||||
vcs_stable_14_0=HttpsGithubComKeymanappKeyman
|
||||
|
||||
# Stable 15.0 Build Configurations
|
||||
|
||||
bc_stable_15_0_android=(KeymanAndroid_Build)
|
||||
bc_stable_15_0_ios=(Keyman_iOS_Master)
|
||||
bc_stable_15_0_linux=(KeymanLinux_Master deb-release-packaging_GitHub)
|
||||
bc_stable_15_0_mac=(KeymanMac_Master)
|
||||
bc_stable_15_0_windows=(Keyman_Build)
|
||||
bc_stable_15_0_web=(Keymanweb_Build)
|
||||
|
||||
vcs_stable_15_0=HttpsGithubComKeymanappKeyman
|
||||
|
||||
# Stable 16.0 Build Configurations
|
||||
|
||||
bc_stable_16_0_android=(KeymanAndroid_Build)
|
||||
bc_stable_16_0_ios=(Keyman_iOS_Master)
|
||||
bc_stable_16_0_linux=(KeymanLinux_Master deb-release-packaging_GitHub)
|
||||
bc_stable_16_0_mac=(KeymanMac_Master)
|
||||
bc_stable_16_0_windows=(Keyman_Build)
|
||||
bc_stable_16_0_web=(Keymanweb_Build)
|
||||
bc_stable_16_0_developer=(Keyman_Developer_Release)
|
||||
|
||||
vcs_stable_16_0=HttpsGithubComKeymanappKeyman
|
||||
|
||||
# Stable 17.0 Build Configurations
|
||||
|
||||
bc_stable_17_0_android=(KeymanAndroid_Build)
|
||||
bc_stable_17_0_ios=(Keyman_iOS_Master)
|
||||
bc_stable_17_0_linux=(KeymanLinux_Master deb-release-packaging_GitHub)
|
||||
bc_stable_17_0_mac=(KeymanMac_Master)
|
||||
bc_stable_17_0_windows=(Keyman_Build)
|
||||
bc_stable_17_0_web=(Keymanweb_Build)
|
||||
bc_stable_17_0_developer=(Keyman_Developer_Release)
|
||||
|
||||
vcs_stable_17_0=HttpsGithubComKeymanappKeyman
|
||||
|
||||
# Stable 17.0 Build Configurations
|
||||
# Stable 18.0 Build Configurations
|
||||
|
||||
bc_stable_18_0_android=(KeymanAndroid_Build)
|
||||
bc_stable_18_0_ios=(Keyman_iOS_Master)
|
||||
|
|
@ -147,3 +97,15 @@ bc_stable_18_0_web=(Keymanweb_Build)
|
|||
bc_stable_18_0_developer=(Keyman_Developer_Release)
|
||||
|
||||
vcs_stable_18_0=HttpsGithubComKeymanappKeyman
|
||||
|
||||
# Stable 19.0 Build Configurations
|
||||
|
||||
bc_stable_19_0_android=(KeymanAndroid_Build)
|
||||
bc_stable_19_0_ios=(Keyman_iOS_Master)
|
||||
bc_stable_19_0_linux=(KeymanLinux_Master deb-release-packaging_GitHub)
|
||||
bc_stable_19_0_mac=(KeymanMac_Master)
|
||||
bc_stable_19_0_windows=(Keyman_Build)
|
||||
bc_stable_19_0_web=(Keymanweb_Build)
|
||||
bc_stable_19_0_developer=(Keyman_Developer_Release)
|
||||
|
||||
vcs_stable_19_0=HttpsGithubComKeymanappKeyman
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@
|
|||
#
|
||||
# TODO: refactor with /resources/build/win/zip.inc.sh
|
||||
|
||||
# zip/7z to create an archive with the following parameters (in order)
|
||||
# Add files to create a zip/7z archive with the following parameters (in order)
|
||||
# [zip filename]
|
||||
# [list of flags to pass to zip command. Flags start with a single-dash
|
||||
# [list of flags to pass to zip command] Flags start with a single-dash
|
||||
# -x@filename for a file containing list of files to exclude from the archive
|
||||
# -* all other flags]
|
||||
# -* all other flags
|
||||
# Flags passed in are treated as zip parameters, and internally converterted to 7z flags as applicable
|
||||
# [list of files to include in zip]
|
||||
function zip_files() {
|
||||
function add_zip_files() {
|
||||
|
||||
# Parse parameters
|
||||
|
||||
|
|
@ -23,15 +24,50 @@ function zip_files() {
|
|||
shift
|
||||
|
||||
# Parse rest of parameters
|
||||
FLAGS=()
|
||||
INCLUDE=()
|
||||
local ZIP_FLAGS=()
|
||||
local SEVENZ_FLAGS=('a') # 7z requires a command
|
||||
local INCLUDE=()
|
||||
while [[ $# -gt 0 ]] ; do
|
||||
case "$1" in
|
||||
-*)
|
||||
# zip/7z flags
|
||||
FLAGS+=($1)
|
||||
-r)
|
||||
# recursive paths - Identical flag to zip and 7z
|
||||
ZIP_FLAGS+=($1)
|
||||
SEVENZ_FLAGS+=($1)
|
||||
shift
|
||||
;;
|
||||
-x@*)
|
||||
# Filename for a file containing list of files to exclude from the archive - Identical flag to zip and 7z
|
||||
ZIP_FLAGS+=($1)
|
||||
SEVENZ_FLAGS+=($1)
|
||||
shift
|
||||
;;
|
||||
|
||||
# Zip flags that have a corresponding 7z flag
|
||||
-q)
|
||||
# quiet mode -> disable progress indicator, set output log level 0
|
||||
ZIP_FLAGS+=($1)
|
||||
SEVENZ_FLAGS+=("-bd")
|
||||
SEVENZ_FLAGS+=("-bb0")
|
||||
shift
|
||||
;;
|
||||
-[0123456789])
|
||||
# Compression level where
|
||||
# -0 indicates no compression
|
||||
# -1 indicates low compression (fastest)
|
||||
# -9 indicates ultra compression (slowest)
|
||||
ZIP_FLAGS+=($1)
|
||||
if [[ $1 =~ -([0-9]) ]]; then
|
||||
SEVENZ_FLAGS+=("-mx${BASH_REMATCH[1]}")
|
||||
fi
|
||||
shift;
|
||||
;;
|
||||
|
||||
-*)
|
||||
# Remaining zip flags that don't apply to 7z
|
||||
ZIP_FLAGS+=($1)
|
||||
shift
|
||||
;;
|
||||
|
||||
*)
|
||||
# files to include in the archive
|
||||
INCLUDE+=($1)
|
||||
|
|
@ -40,8 +76,7 @@ function zip_files() {
|
|||
esac
|
||||
done
|
||||
|
||||
COMPRESS_CMD=zip
|
||||
SEVENZ_CMD=
|
||||
local COMPRESS_CMD=zip
|
||||
if ! command -v zip 2>&1 > /dev/null; then
|
||||
# Fallback to 7z
|
||||
if [[ -z "${SEVENZ+x}" ]]; then
|
||||
|
|
@ -53,15 +88,18 @@ function zip_files() {
|
|||
SEVENZ=7z
|
||||
;;
|
||||
esac
|
||||
|
||||
# 7z command to add files
|
||||
COMPRESS_CMD="${SEVENZ}"
|
||||
SEVENZ_CMD="a"
|
||||
fi
|
||||
|
||||
# 7z command to add files so clear zip flags
|
||||
COMPRESS_CMD="${SEVENZ}"
|
||||
ZIP_FLAGS=()
|
||||
else
|
||||
# Using zip so clear 7z flags
|
||||
SEVENZ_FLAGS=()
|
||||
fi
|
||||
|
||||
# Create archive
|
||||
builder_echo_debug "${COMPRESS_CMD} ${SEVENZ_CMD} ${FLAGS[@]} ${ZIP_FILE} ${INCLUDE[@]}"
|
||||
"${COMPRESS_CMD}" ${SEVENZ_CMD} ${FLAGS[@]} ${ZIP_FILE} ${INCLUDE[@]}
|
||||
# builder_echo_debug "${COMPRESS_CMD} ${SEVENZ_FLAGS[@]} ${ZIP_FLAGS[@]} ${ZIP_FILE} ${INCLUDE[@]}"
|
||||
"${COMPRESS_CMD}" ${SEVENZ_FLAGS[@]} ${ZIP_FLAGS[@]} ${ZIP_FILE} ${INCLUDE[@]}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -181,28 +181,30 @@ function builder_heading() {
|
|||
|
||||
|
||||
builder_echo() {
|
||||
local color=white message= mark= block= action= do_output=true
|
||||
local color=white message= mark= block= action= do_output=true test=
|
||||
local echo_target=echo
|
||||
|
||||
if [[ $# -gt 1 ]]; then
|
||||
if [[ $1 =~ ^(white|grey|green|success|blue|heading|yellow|warning|red|error|purple|brightwhite|teal|debug|setmark)$ ]]; then
|
||||
color="$1"
|
||||
shift
|
||||
elif [[ $1 == "start" ]]; then
|
||||
elif [[ $1 == "start" ]] || [[ $1 == "startTest" ]]; then
|
||||
# builder_echo start block message
|
||||
action="$1"
|
||||
test="$1"
|
||||
block="$2"
|
||||
shift 2
|
||||
action="start"
|
||||
color="heading"
|
||||
if ! builder_is_running_on_teamcity && builder_is_child_build; then
|
||||
do_output=${_builder_debug_internal:-false}
|
||||
fi
|
||||
elif [[ $1 == "end" ]]; then
|
||||
elif [[ $1 == "end" ]] || [[ $1 == "endTest" ]]; then
|
||||
# builder_echo end block status message
|
||||
action="$1"
|
||||
test="$1"
|
||||
block="$2"
|
||||
color="$3"
|
||||
shift 3
|
||||
action="end"
|
||||
if [[ "${color}" != "error" ]] && ! builder_is_running_on_teamcity && builder_is_child_build; then
|
||||
do_output=${_builder_debug_internal:-false}
|
||||
fi
|
||||
|
|
@ -211,7 +213,11 @@ builder_echo() {
|
|||
message="$*"
|
||||
|
||||
if [[ "${action}" == "start" ]] && builder_is_running_on_teamcity; then
|
||||
$echo_target -e "##teamcity[blockOpened name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']"
|
||||
if [[ "${test}" == "startTest" ]]; then
|
||||
$echo_target -e "##teamcity[testSuiteStarted name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']"
|
||||
else
|
||||
$echo_target -e "##teamcity[blockOpened name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']"
|
||||
fi
|
||||
fi
|
||||
|
||||
if ${do_output}; then
|
||||
|
|
@ -241,7 +247,11 @@ builder_echo() {
|
|||
fi
|
||||
|
||||
if [[ "${action}" == "end" ]] && builder_is_running_on_teamcity; then
|
||||
$echo_target -e "##teamcity[blockClosed name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']"
|
||||
if [[ "${test}" == "endTest" ]]; then
|
||||
$echo_target -e "##teamcity[testSuiteFinished name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']"
|
||||
else
|
||||
$echo_target -e "##teamcity[blockClosed name='|[${THIS_SCRIPT_IDENTIFIER}|] ${block}']"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@
|
|||
"homepage": "https://github.com/keymanapp/keyman",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"gosh": "./gosh.js"
|
||||
"gosh": "gosh.js"
|
||||
},
|
||||
"private": true,
|
||||
"files": [
|
||||
|
|
@ -18,6 +18,7 @@
|
|||
"type": "git",
|
||||
"url": "git+https://github.com/keymanapp/keyman.git"
|
||||
},
|
||||
"scripts": {},
|
||||
"bugs": {
|
||||
"url": "https://github.com/keymanapp/keyman/issues"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ and then run the build, e.g.
|
|||
|
||||
```bash
|
||||
cd /Develop
|
||||
resources/teamcity/keyman-linux-test.sh configure,build,test
|
||||
resources/teamcity/linux/keyman-linux-test.sh configure,build,test
|
||||
```
|
||||
|
||||
NOTE: by default this will run the build as `root` in the container,
|
||||
|
|
|
|||
|
|
@ -9,9 +9,10 @@ linux_clean_action() {
|
|||
|
||||
# Install required dependencies for building Keyman on Linux.
|
||||
linux_install_dependencies_action() {
|
||||
builder_heading "Installing dependencies"
|
||||
builder_echo start "install dependencies" "Installing dependencies"
|
||||
. "${KEYMAN_ROOT}/linux/scripts/package-build.inc.sh"
|
||||
checkAndInstallRequirements
|
||||
builder_echo end "install dependencies" success "Finished installing dependencies"
|
||||
}
|
||||
|
||||
# Install additional dependencies required for determining test coverage.
|
||||
|
|
@ -46,7 +47,8 @@ linux_build_action() {
|
|||
|
||||
# Run unit tests for Keyman for Linux.
|
||||
linux_unit_tests_action() {
|
||||
builder_echo start unit_tests "Running unit tests"
|
||||
builder_echo startTest unit_tests "Running unit tests"
|
||||
|
||||
rm -f /tmp/ibus-engine-keyman.log
|
||||
rm -f /tmp/ibus-daemon.log
|
||||
# symlink might point to wrong location, so delete it - will be re-created during tests
|
||||
|
|
@ -55,5 +57,5 @@ linux_unit_tests_action() {
|
|||
export NO_AT_BRIDGE=1
|
||||
# shellcheck disable=SC2068
|
||||
"${KEYMAN_ROOT}/linux/build.sh" test $@
|
||||
builder_echo end unit_tests success "Finished running unit tests"
|
||||
builder_echo endTest unit_tests success "Finished running unit tests"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh"
|
||||
. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
|
|
@ -22,7 +22,7 @@ builder_describe \
|
|||
"configure install dependencies" \
|
||||
"build make a release build" \
|
||||
"test run unit tests" \
|
||||
"publish make a source tarball" \
|
||||
"publish make a source tarball and publish to downloads and launchpad" \
|
||||
"--gpgkey=GPGKEYGRIP GPG key for signing" \
|
||||
"--gpgpw=GPGKEYPW GPG key passphrase" \
|
||||
"--rsync-path=RSYNC_PATH rsync path on remote server" \
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh"
|
||||
. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
|
|
@ -6,7 +6,7 @@
|
|||
## START STANDARD BUILD SCRIPT INCLUDE
|
||||
# adjust relative paths as necessary
|
||||
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
||||
. "${THIS_SCRIPT%/*}/../../resources/build/builder.inc.sh"
|
||||
. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
# shellcheck disable=SC2154
|
||||
|
|
@ -1,7 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (C) 2025 SIL International. All rights reserved.
|
||||
# Distributed under the MIT License. See LICENSE.md file in the project
|
||||
# root for full license information.
|
||||
# Keyman is copyright (C) SIL Global. MIT License.
|
||||
#
|
||||
# TC build script to build release of KeymanWeb.
|
||||
|
||||
|
|
@ -28,8 +26,8 @@ builder_describe \
|
|||
"configure install dependencies" \
|
||||
"build build Web + embedded" \
|
||||
"publish publish release" \
|
||||
"--s.keyman.com=S_KEYMAN_COM_PATH path to s.keyman.com repository" \
|
||||
"--help.keyman.com=HELP_KEYMAN_COM_PATH path to help.keyman.com repository"
|
||||
"--s.keyman.com=S_KEYMAN_COM path to s.keyman.com repository" \
|
||||
"--help.keyman.com=HELP_KEYMAN_COM path to help.keyman.com repository"
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
@ -40,10 +38,10 @@ function _push_release_to_skeymancom() {
|
|||
# downloads.keyman.com so we can ensure files are available)
|
||||
builder_echo start publish "Publishing release to s.keyman.com"
|
||||
|
||||
cd "${S_KEYMAN_COM_PATH:=${KEYMAN_ROOT}/../s.keyman.com}"
|
||||
cd "${S_KEYMAN_COM:=${KEYMAN_ROOT}/../s.keyman.com}"
|
||||
git pull https://github.com/keymanapp/s.keyman.com.git master
|
||||
cd "${KEYMAN_ROOT}/web"
|
||||
"${KEYMAN_ROOT}/web/ci.sh" prepare:s.keyman.com --s.keyman.com "${S_KEYMAN_COM_PATH}"
|
||||
"${KEYMAN_ROOT}/web/ci.sh" prepare:s.keyman.com --s.keyman.com "${S_KEYMAN_COM}"
|
||||
|
||||
builder_echo end publish success "Finished publishing release to s.keyman.com"
|
||||
}
|
||||
|
|
@ -66,7 +64,7 @@ function _zip_and_upload_artifacts() {
|
|||
function _upload_help() {
|
||||
builder_echo start "upload help" "Uploading new Keyman for Web help to help.keyman.com"
|
||||
|
||||
export HELP_KEYMAN_COM="${HELP_KEYMAN_COM_PATH:-${KEYMAN_ROOT}/../help.keyman.com}"
|
||||
export HELP_KEYMAN_COM="${HELP_KEYMAN_COM:-${KEYMAN_ROOT}/../help.keyman.com}"
|
||||
cd "${KEYMAN_ROOT}/resources/build"
|
||||
"${KEYMAN_ROOT}/resources/build/help-keyman-com.sh" web
|
||||
cd "${KEYMAN_ROOT}/web"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,5 @@
|
|||
#!/usr/bin/env bash
|
||||
# Copyright (C) 2025 SIL International
|
||||
# Distributed under the MIT License. See LICENSE.md file in the project
|
||||
# root for full license information.
|
||||
# Keyman is copyright (C) SIL Global. MIT License.
|
||||
#
|
||||
# TC build script for Keyman Web/Test
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
"type": "module",
|
||||
"main": "build/index.js",
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"devDependencies": {
|
||||
"marked": "^14.1.2",
|
||||
"chalk": "^2.4.2"
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ if(!keymanweb) {
|
|||
|
||||
/**
|
||||
* Do not enclose in an anonymous function, as the compiler may create
|
||||
* global scope variables to replace true, false, null, whcih can then collide
|
||||
* global scope variables to replace true, false, null, which can then collide
|
||||
* with other variables.
|
||||
* Instead, use the --output-wrapper command during optimization, which will
|
||||
* add the anonymous function to enclose all code, including those optimized
|
||||
|
|
@ -106,7 +106,7 @@ if(!keymanweb) {
|
|||
* @param {Event} _id keyboard selection event
|
||||
* @return {boolean}
|
||||
*/
|
||||
readonly _SelectKeyboard = (_id: Event) => {
|
||||
private readonly _SelectKeyboard = async (_id: Event): Promise<boolean> => {
|
||||
let id: string = '';
|
||||
if(typeof(_id) == 'object') {
|
||||
let t: HTMLElement = null;
|
||||
|
|
@ -135,7 +135,7 @@ if(!keymanweb) {
|
|||
_k.className='selected';
|
||||
}
|
||||
this._KMWSel = _k;
|
||||
keymanweb.setActiveKeyboard(_name,_lgc);
|
||||
await keymanweb.setActiveKeyboard(_name,_lgc);
|
||||
} else {
|
||||
_name=null;
|
||||
}
|
||||
|
|
@ -252,7 +252,7 @@ if(!keymanweb) {
|
|||
*
|
||||
* @param {Event} e event
|
||||
*/
|
||||
readonly _SelectorMouseOut = (e: MouseEvent) => {
|
||||
private readonly _SelectorMouseOut = (e: MouseEvent) => {
|
||||
if(keymanweb.activatingUI) {
|
||||
keymanweb.activatingUI(0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -450,15 +450,15 @@ if(!keymanweb) {
|
|||
* @param {Object} e event
|
||||
* Description Change active keyboard in response to user selection event
|
||||
*/
|
||||
readonly SelectKeyboardChange = (e: Event) => {
|
||||
private readonly SelectKeyboardChange = async (e: Event) => {
|
||||
keymanweb.activatingUI(true);
|
||||
|
||||
if(this.KeyboardSelector.value != '-') {
|
||||
const i=this.KeyboardSelector.selectedIndex;
|
||||
const t=this.KeyboardSelector.options[i].value.split(':');
|
||||
keymanweb.setActiveKeyboard(t[0],t[1]);
|
||||
await keymanweb.setActiveKeyboard(t[0],t[1]);
|
||||
} else {
|
||||
keymanweb.setActiveKeyboard('');
|
||||
await keymanweb.setActiveKeyboard('');
|
||||
}
|
||||
|
||||
//if(osk['show']) osk['show'](osk['isEnabled']()); handled by keyboard change event???
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ if(!keymanweb) {
|
|||
/**
|
||||
* Toggle a single keyboard on or off - KMW button control event
|
||||
**/
|
||||
readonly switchSingleKbd = () => {
|
||||
readonly switchSingleKbd = async () => {
|
||||
const _v = keymanweb.getActiveKeyboard() == '';
|
||||
let nLastKbd=0, kbdName='', lgCode='';
|
||||
|
||||
|
|
@ -202,10 +202,10 @@ if(!keymanweb) {
|
|||
|
||||
kbdName = this.keyboards[nLastKbd]._InternalName;
|
||||
lgCode = this.keyboards[nLastKbd]._LanguageCode;
|
||||
keymanweb.setActiveKeyboard(kbdName,lgCode);
|
||||
await keymanweb.setActiveKeyboard(kbdName,lgCode);
|
||||
this.lastActiveKeyboard = nLastKbd;
|
||||
} else {
|
||||
keymanweb.setActiveKeyboard('');
|
||||
await keymanweb.setActiveKeyboard('');
|
||||
}
|
||||
|
||||
if(this.kbdButton) {
|
||||
|
|
@ -216,7 +216,7 @@ if(!keymanweb) {
|
|||
/**
|
||||
* Switch to the next keyboard in the list - KMW button control event
|
||||
**/
|
||||
readonly switchNextKbd = () => {
|
||||
readonly switchNextKbd = async () => {
|
||||
let _v = (keymanweb.getActiveKeyboard() == '');
|
||||
let kbdName='', lgCode='';
|
||||
|
||||
|
|
@ -227,16 +227,16 @@ if(!keymanweb) {
|
|||
|
||||
kbdName = this.keyboards[0]._InternalName;
|
||||
lgCode = this.keyboards[0]._LanguageCode;
|
||||
keymanweb.setActiveKeyboard(kbdName,lgCode);
|
||||
await keymanweb.setActiveKeyboard(kbdName,lgCode);
|
||||
this.lastActiveKeyboard = 0;
|
||||
} else {
|
||||
if(this.lastActiveKeyboard == this.keyboards.length-1) {
|
||||
keymanweb.setActiveKeyboard('');
|
||||
await keymanweb.setActiveKeyboard('');
|
||||
_v = false;
|
||||
} else {
|
||||
kbdName = this.keyboards[++this.lastActiveKeyboard]._InternalName;
|
||||
lgCode = this.keyboards[this.lastActiveKeyboard]._LanguageCode;
|
||||
keymanweb.setActiveKeyboard(kbdName,lgCode);
|
||||
await keymanweb.setActiveKeyboard(kbdName,lgCode);
|
||||
_v = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -558,7 +558,7 @@ if(!keymanweb) {
|
|||
* @param {number} _kbd
|
||||
* Description Select a keyboard from the drop down menu
|
||||
**/
|
||||
selectKbd(_kbd: number) {
|
||||
private async selectKbd(_kbd: number): Promise<boolean> {
|
||||
let _name,_lgCode;
|
||||
if(_kbd < 0) {
|
||||
_name = '';
|
||||
|
|
@ -568,7 +568,7 @@ if(!keymanweb) {
|
|||
_lgCode = this.keyboards[_kbd]._LanguageCode;
|
||||
}
|
||||
|
||||
keymanweb.setActiveKeyboard(_name,_lgCode);
|
||||
await keymanweb.setActiveKeyboard(_name,_lgCode);
|
||||
keymanweb.focusLastActiveElement();
|
||||
this.kbdButton._setSelected(_name != '');
|
||||
if(_kbd >= 0) {
|
||||
|
|
|
|||
|
|
@ -820,7 +820,7 @@ if(!keymanweb) {
|
|||
* @param {boolean} updateKeyman
|
||||
* @return {boolean}
|
||||
**/
|
||||
selectKeyboard(event: Event, lang: LanguageEntry, kbd: KeyboardDetail, updateKeyman: boolean) {
|
||||
private async selectKeyboard(event: Event, lang: LanguageEntry, kbd: KeyboardDetail, updateKeyman: boolean) {
|
||||
keymanweb.activatingUI(true);
|
||||
|
||||
if(this.selectedLanguage) {
|
||||
|
|
@ -841,7 +841,7 @@ if(!keymanweb) {
|
|||
// Return focus to input area and activate the selected keyboard
|
||||
this.addKeyboardToList(lang, kbd);
|
||||
if(updateKeyman) {
|
||||
keymanweb.setActiveKeyboard(kbd.InternalName, kbd.LanguageCode).then(() => {
|
||||
await keymanweb.setActiveKeyboard(kbd.InternalName, kbd.LanguageCode).then(() => {
|
||||
// Restore focus _after_ the keyboard finishes loading.
|
||||
this.setLastFocus();
|
||||
});
|
||||
|
|
@ -943,7 +943,7 @@ if(!keymanweb) {
|
|||
* @return {boolean}
|
||||
* Description Update the UI when all keyboards disabled by user
|
||||
**/
|
||||
readonly offButtonClickEvent = (event: Event) => {
|
||||
private readonly offButtonClickEvent = async (event: Event) => {
|
||||
if(this.toolbarNode.className != 'kmw_controls_disabled') {
|
||||
this.hideKeyboardsForLanguage(null);
|
||||
if(this.selectedLanguage) {
|
||||
|
|
@ -959,7 +959,7 @@ if(!keymanweb) {
|
|||
|
||||
// Return the focus to the input area and set the active keyboard to nothing
|
||||
this.setLastFocus();
|
||||
keymanweb.setActiveKeyboard('','');
|
||||
await keymanweb.setActiveKeyboard('','');
|
||||
|
||||
//Save current state when deselecting a keyboard (may not be needed)
|
||||
this.saveCookie();
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ type Options = {
|
|||
};
|
||||
|
||||
export class KeymanSentryManager {
|
||||
keymanPlatform: string;
|
||||
_enabled: boolean = true;
|
||||
private keymanPlatform: string;
|
||||
private _enabled: boolean = true;
|
||||
|
||||
static STANDARD_ALIASABLE_FILES = {
|
||||
'keymanweb.js': 'keymanweb.js',
|
||||
|
|
@ -34,7 +34,7 @@ export class KeymanSentryManager {
|
|||
}
|
||||
|
||||
// If we've recognized one of our source files,
|
||||
aliasFilename(filename: string): string|null {
|
||||
private aliasFilename(filename: string): string|null {
|
||||
if(!this.mayAlias(filename)) {
|
||||
return null;
|
||||
}
|
||||
|
|
@ -49,14 +49,14 @@ export class KeymanSentryManager {
|
|||
}
|
||||
}
|
||||
|
||||
mayAlias(filename: string): boolean {
|
||||
private mayAlias(filename: string): boolean {
|
||||
return !!KeymanSentryManager.STANDARD_ALIASABLE_FILES[filename];
|
||||
}
|
||||
|
||||
// Filters all expected but unnecessary path prefixes, affixes, and suffixes reported by Sentry from our products.
|
||||
// This allows us to mask all different sorts of installations with a single uploaded path.
|
||||
// Modifies original object.
|
||||
pathFilter(event: any) {
|
||||
private pathFilter(event: any) {
|
||||
// Get the underlying JS error.
|
||||
let exception = event.exception;
|
||||
if(!exception) {
|
||||
|
|
@ -90,7 +90,7 @@ export class KeymanSentryManager {
|
|||
}
|
||||
|
||||
// Attaches some useful debugging information to the specified object, pass-by-reference style.
|
||||
attachEventMetadata(event: any) {
|
||||
private attachEventMetadata(event: any) {
|
||||
// Ensure that the 'extra' object exists. (May not exist for synthetic/custom Errors.)
|
||||
event.extra = event.extra || {};
|
||||
//@ts-ignore
|
||||
|
|
@ -100,7 +100,7 @@ export class KeymanSentryManager {
|
|||
|
||||
// Sanitizes the event object (in-place) to remove sensitive information
|
||||
// from the breadcrumbs and url (for embedded KeymanWeb)
|
||||
sanitizeEvent(event: any) {
|
||||
private sanitizeEvent(event: any) {
|
||||
if (event && event.breadcrumbs) {
|
||||
event.breadcrumbs.forEach((b: any) => {
|
||||
if (b.category == 'navigation') {
|
||||
|
|
@ -123,7 +123,7 @@ export class KeymanSentryManager {
|
|||
* Also will sanitize the Sentry event.
|
||||
* @param event A Sentry-generated event
|
||||
*/
|
||||
prepareEvent(event: any): boolean {
|
||||
private prepareEvent(event: any): boolean {
|
||||
this.pathFilter(event);
|
||||
this.attachEventMetadata(event);
|
||||
this.sanitizeEvent(event);
|
||||
|
|
@ -148,7 +148,7 @@ export class KeymanSentryManager {
|
|||
* if we want to prevent the event from being sent to the server.
|
||||
* @param event
|
||||
*/
|
||||
prepareEventDebugWrapper(event: any) {
|
||||
public prepareEventDebugWrapper(event: any) {
|
||||
if(DEBUG) {
|
||||
try {
|
||||
if(this.prepareEvent(event)) {
|
||||
|
|
@ -178,7 +178,7 @@ export class KeymanSentryManager {
|
|||
*
|
||||
* https://stackoverflow.com/a/53214615/1836776
|
||||
*/
|
||||
initConsole() {
|
||||
private initConsole() {
|
||||
// creating function declarations for better stacktraces (otherwise they'd be anonymous function expressions)
|
||||
let oldConsoleError = console.error;
|
||||
let _this = this;
|
||||
|
|
@ -223,7 +223,7 @@ export class KeymanSentryManager {
|
|||
}
|
||||
}
|
||||
|
||||
init() {
|
||||
public init() {
|
||||
// Do the actual Sentry initialization.
|
||||
//@ts-ignore
|
||||
Sentry.init({
|
||||
|
|
@ -238,11 +238,11 @@ export class KeymanSentryManager {
|
|||
this.initConsole();
|
||||
}
|
||||
|
||||
get enabled(): boolean {
|
||||
public get enabled(): boolean {
|
||||
return this._enabled;
|
||||
}
|
||||
|
||||
set enabled(value: boolean) {
|
||||
public set enabled(value: boolean) {
|
||||
this._enabled = value;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -987,10 +987,11 @@ begin
|
|||
executeResult := WaitForElevatedConfiguration(0, '-ikp');
|
||||
if (executeResult <> 0) then
|
||||
begin
|
||||
TKeymanSentryClient.Client.MessageEvent
|
||||
(Sentry.Client.SENTRY_LEVEL_ERROR,
|
||||
'Executing kmshell process to install keyboard packages failed:"' +
|
||||
IntToStr(Ord(executeResult)) + '"');
|
||||
TKeymanSentryClient.Breadcrumb('error',
|
||||
'Executing kmshell process to install keyboard packages failed"' +
|
||||
IntToStr(Ord(executeResult)) + '"', 'update');
|
||||
KL.Log('InstallingState.LaunchInstallPackageProcess failed executing kmshell ' +
|
||||
'process to install keyboard packages: "' + IntToStr(Ord(executeResult)) + '"');
|
||||
ChangeState(IdleState);
|
||||
end;
|
||||
end
|
||||
|
|
@ -1042,9 +1043,10 @@ begin
|
|||
if not FResult then
|
||||
begin
|
||||
bucStateContext.RemoveCachedFiles;
|
||||
TKeymanSentryClient.Client.MessageEvent(Sentry.Client.SENTRY_LEVEL_ERROR,
|
||||
'Executing kmshell process to install failed:"' +
|
||||
IntToStr(Ord(FResult)) + '"');
|
||||
TKeymanSentryClient.Breadcrumb('error',
|
||||
'InstallingState.DoInstallKeyman: failed executing kmshell or file not found', 'update');
|
||||
KL.Log('InstallingState.DoInstallKeyman failed executing kmshell ' +
|
||||
'or the update was not found in the cache');
|
||||
ChangeState(IdleState);
|
||||
end;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue