mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-13 17:37:31 +00:00
Merge branch 'feat/developer/kmc-convert' into feat/developer/kmc-convert-useArtifacts
This commit is contained in:
commit
b50d8e9c09
673 changed files with 10287 additions and 8159 deletions
|
|
@ -5,9 +5,7 @@ module.exports = {
|
|||
es2021: true,
|
||||
node: true,
|
||||
},
|
||||
// TODO: we need to move to the following, but this gets us started with infrastructure without breaking our build
|
||||
// extends: [ "eslint:recommended", "plugin:@typescript-eslint/eslint-recommended"],
|
||||
extends: ["plugin:@typescript-eslint/eslint-recommended"],
|
||||
extends: [ "plugin:@typescript-eslint/eslint-recommended"],
|
||||
parser: "@typescript-eslint/parser",
|
||||
parserOptions: {
|
||||
ecmaVersion: "latest",
|
||||
|
|
|
|||
9
.gitattributes
vendored
Normal file
9
.gitattributes
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Always use LF for Dockerfile - otherwise HEREDOCs in Dockerfile
|
||||
# will not work on Windows - we end up with a filename `foo\r` for
|
||||
# the command
|
||||
# ```Dockerfile
|
||||
# RUN <<EOF cat > foo
|
||||
# echo "Hello world"
|
||||
# EOF
|
||||
# ```
|
||||
Dockerfile text eol=lf
|
||||
|
|
@ -25,13 +25,13 @@ runs:
|
|||
using: 'composite'
|
||||
steps:
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: keyman-srcpkg
|
||||
path: artifacts/keyman-srcpkg
|
||||
|
||||
- name: Build
|
||||
uses: sillsdev/gha-ubuntu-packaging@7b56f50d5d5537e9e9cafd3f6139ec6da69cfcda # v1.1
|
||||
uses: sillsdev/gha-ubuntu-packaging@556b268762be9bea0f39861a7392587211ef6a58 # v2.0.1
|
||||
with:
|
||||
dist: "${{ inputs.dist }}"
|
||||
platform: "${{ inputs.arch }}"
|
||||
|
|
@ -49,7 +49,7 @@ runs:
|
|||
echo '```' >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Store binary packages
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: keyman-binarypkgs-${{ inputs.dist }}_${{ inputs.arch }}
|
||||
path: |
|
||||
|
|
|
|||
54
.github/labeler.yml
vendored
54
.github/labeler.yml
vendored
|
|
@ -11,51 +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/**
|
||||
- resources/**
|
||||
|
||||
common/models/templates/: web/src/engine/predictive-text/templates/**
|
||||
common/models/wordbreakers/: web/src/engine/predictive-text/wordbreakers/**
|
||||
|
||||
common/resources/: resources/**
|
||||
- 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/**
|
||||
|
||||
|
|
@ -65,12 +70,21 @@ oem/fv/android/: oem/firstvoices/android/**
|
|||
oem/fv/ios/: oem/firstvoices/ios/**
|
||||
oem/fv/windows/: oem/firstvoices/windows/**
|
||||
|
||||
web/: web/**
|
||||
resources/: resources/**
|
||||
|
||||
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/**
|
||||
|
|
|
|||
11
.github/multi-labeler.yml
vendored
11
.github/multi-labeler.yml
vendored
|
|
@ -33,6 +33,9 @@ labels:
|
|||
- label: 'fix'
|
||||
matcher:
|
||||
title: '^fix(\(|:)'
|
||||
- label: 'maint'
|
||||
matcher:
|
||||
title: '^maint(\(|:)'
|
||||
- label: 'refactor'
|
||||
matcher:
|
||||
title: '^refactor(\(|:)'
|
||||
|
|
@ -57,11 +60,6 @@ labels:
|
|||
matcher:
|
||||
title: '(🍒|:cherries:)'
|
||||
|
||||
# long-lived feature branches
|
||||
- label: 'feature-branch'
|
||||
matcher:
|
||||
branch: '^feature-.+'
|
||||
|
||||
#
|
||||
# Scopes that we look for in the PR title
|
||||
#
|
||||
|
|
@ -90,6 +88,9 @@ labels:
|
|||
- label: 'oem/'
|
||||
matcher:
|
||||
title: '\(.*oem.*\):'
|
||||
- label: 'resources/'
|
||||
matcher:
|
||||
title: '\(.*resources.*\):'
|
||||
- label: 'web/'
|
||||
matcher:
|
||||
title: '\(.*web.*\):'
|
||||
|
|
|
|||
10
.github/workflows/api-verification.yml
vendored
10
.github/workflows/api-verification.yml
vendored
|
|
@ -15,7 +15,7 @@ jobs:
|
|||
name: Verify API for libkeymancore*.so
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
VERSION: ${{ steps.environment_step.outputs.VERSION }}
|
||||
KEYMAN_VERSION: ${{ steps.environment_step.outputs.KEYMAN_VERSION }}
|
||||
PRERELEASE_TAG: ${{ steps.environment_step.outputs.PRERELEASE_TAG }}
|
||||
GIT_SHA: ${{ steps.environment_step.outputs.GIT_SHA }}
|
||||
GIT_BASE: ${{ steps.environment_step.outputs.GIT_BASE }}
|
||||
|
|
@ -26,7 +26,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Restore artifacts
|
||||
uses: actions/cache/restore@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
artifacts
|
||||
|
|
@ -53,7 +53,7 @@ jobs:
|
|||
-f context="$STATUS_CONTEXT"
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
ref: '${{ steps.environment_step.outputs.GIT_SHA }}'
|
||||
fetch-depth: 0
|
||||
|
|
@ -69,7 +69,7 @@ jobs:
|
|||
run: |
|
||||
echo "Verify API for libkeymancore*.so (${{ steps.environment_step.outputs.GIT_BRANCH }}, branch ${{ steps.environment_step.outputs.GIT_BASE_BRANCH }}, by ${{ steps.environment_step.outputs.GIT_USER }}):" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
BIN_PACKAGE=$(ls "${GITHUB_WORKSPACE}/artifacts/" | grep "${PKG_NAME}[0-9]*_${{ steps.environment_step.outputs.VERSION }}-1${{ steps.environment_step.outputs.PRERELEASE_TAG }}+$(lsb_release -c -s)1_amd64.deb")
|
||||
BIN_PACKAGE=$(ls "${GITHUB_WORKSPACE}/artifacts/" | grep "${PKG_NAME}[0-9]*_${{ steps.environment_step.outputs.KEYMAN_VERSION }}-1${{ steps.environment_step.outputs.PRERELEASE_TAG }}+$(lsb_release -c -s)1_amd64.deb")
|
||||
cd ${{ github.workspace }}/keyman/linux
|
||||
./scripts/deb-packaging.sh \
|
||||
--gha \
|
||||
|
|
@ -79,7 +79,7 @@ jobs:
|
|||
verify 2>> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Archive .symbols file
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: libkeymancore.symbols
|
||||
path: ${{ github.workspace }}/keyman/linux/debian/tmp/DEBIAN/symbols
|
||||
|
|
|
|||
4
.github/workflows/crowdin.yml
vendored
4
.github/workflows/crowdin.yml
vendored
|
|
@ -13,10 +13,10 @@ jobs:
|
|||
steps:
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: crowdin action
|
||||
uses: crowdin/github-action@1.3.2
|
||||
uses: crowdin/github-action@2.7.0
|
||||
with:
|
||||
upload_sources: true
|
||||
|
||||
|
|
|
|||
45
.github/workflows/deb-packaging.yml
vendored
45
.github/workflows/deb-packaging.yml
vendored
|
|
@ -37,11 +37,11 @@ jobs:
|
|||
if: github.repository == 'keymanapp/keyman'
|
||||
runs-on: ubuntu-24.04
|
||||
outputs:
|
||||
VERSION: ${{ steps.version_step.outputs.VERSION }}
|
||||
KEYMAN_VERSION: ${{ steps.version_step.outputs.KEYMAN_VERSION }}
|
||||
PRERELEASE_TAG: ${{ steps.prerelease_tag.outputs.PRERELEASE_TAG }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
ref: '${{ github.event.client_payload.buildSha }}'
|
||||
|
||||
|
|
@ -72,10 +72,10 @@ jobs:
|
|||
- name: Build source package
|
||||
id: build_source_package
|
||||
run: |
|
||||
export TIER=$(cat TIER.md)
|
||||
export KEYMAN_TIER=$(cat TIER.md)
|
||||
export GHA_TEST_BUILD="${{ github.event.client_payload.isTestBuild }}"
|
||||
export GHA_BRANCH="${{ github.event.client_payload.branch }}"
|
||||
echo "TIER=$TIER" >> $GITHUB_ENV
|
||||
echo "KEYMAN_TIER=$KEYMAN_TIER" >> $GITHUB_ENV
|
||||
echo "GHA_TEST_BUILD=${GHA_TEST_BUILD}" >> $GITHUB_ENV
|
||||
echo "GHA_BRANCH=${GHA_BRANCH}" >> $GITHUB_ENV
|
||||
cd linux
|
||||
|
|
@ -87,7 +87,7 @@ jobs:
|
|||
run: |
|
||||
THIS_SCRIPT="$GITHUB_WORKSPACE/.github/workflows/deb-packaging.yml"
|
||||
. "${THIS_SCRIPT%/*}/../../resources/build/build-utils.sh"
|
||||
echo "VERSION=$VERSION" >> $GITHUB_OUTPUT
|
||||
echo "KEYMAN_VERSION=${KEYMAN_VERSION:-${VERSION}}" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Set prerelease tag as output parameter
|
||||
id: prerelease_tag
|
||||
|
|
@ -103,16 +103,16 @@ jobs:
|
|||
- name: Output which branch or PR we're building plus name of .dsc file
|
||||
run: |
|
||||
if [ "${{ github.event.client_payload.isTestBuild }}" == "true" ]; then
|
||||
echo ":checkered_flag: **Test build of version ${{ steps.version_step.outputs.VERSION }} for ${{ github.event.client_payload.branch }}**" >> $GITHUB_STEP_SUMMARY
|
||||
echo ":checkered_flag: **Test build of version ${{ steps.version_step.outputs.KEYMAN_VERSION }} for ${{ github.event.client_payload.branch }}**" >> $GITHUB_STEP_SUMMARY
|
||||
else
|
||||
echo ":ship: **Release build of ${{ github.event.client_payload.branch }} branch (${{ github.event.client_payload.branch}}), version ${{ steps.version_step.outputs.VERSION }}**" >> $GITHUB_STEP_SUMMARY
|
||||
echo ":ship: **Release build of ${{ github.event.client_payload.branch }} branch (${{ github.event.client_payload.branch}}), version ${{ steps.version_step.outputs.KEYMAN_VERSION }}**" >> $GITHUB_STEP_SUMMARY
|
||||
fi
|
||||
echo "" >> $GITHUB_STEP_SUMMARY
|
||||
echo ":gift: Generated source package:" >> $GITHUB_STEP_SUMMARY
|
||||
echo "- $(find . -name keyman_\*.dsc)" >> $GITHUB_STEP_SUMMARY
|
||||
|
||||
- name: Store source package
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
|
||||
with:
|
||||
name: keyman-srcpkg
|
||||
path: |
|
||||
|
|
@ -131,7 +131,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
ref: '${{ github.event.client_payload.buildSha }}'
|
||||
sparse-checkout: '.github/actions/'
|
||||
|
|
@ -140,7 +140,7 @@ jobs:
|
|||
uses: ./.github/actions/build-binary-packages
|
||||
with:
|
||||
dist: ${{ matrix.dist }}
|
||||
version: ${{ needs.sourcepackage.outputs.VERSION }}
|
||||
version: ${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}
|
||||
prerelease_tag: ${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}
|
||||
deb_fullname: ${{env.DEBFULLNAME}}
|
||||
deb_email: ${{env.DEBEMAIL}}
|
||||
|
|
@ -157,7 +157,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 #v4.1.1
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 #v4.2.2
|
||||
with:
|
||||
ref: '${{ github.event.client_payload.buildSha }}'
|
||||
sparse-checkout: '.github/actions/'
|
||||
|
|
@ -166,7 +166,7 @@ jobs:
|
|||
uses: ./.github/actions/build-binary-packages
|
||||
with:
|
||||
dist: ${{ matrix.dist }}
|
||||
version: ${{ needs.sourcepackage.outputs.VERSION }}
|
||||
version: ${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}
|
||||
prerelease_tag: ${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}
|
||||
deb_fullname: ${{env.DEBFULLNAME}}
|
||||
deb_email: ${{env.DEBEMAIL}}
|
||||
|
|
@ -178,7 +178,7 @@ jobs:
|
|||
|
||||
steps:
|
||||
# - name: Download Artifacts
|
||||
# uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
|
||||
# uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
# with:
|
||||
# path: artifacts
|
||||
# merge-multiple: true
|
||||
|
|
@ -198,7 +198,7 @@ jobs:
|
|||
# autopkgtest -B *.deb keyman_*.dsc -- qemu autopkgtest-jammy-amd64.img
|
||||
- name: Ignore
|
||||
run: |
|
||||
echo "Ignored for now - until working solution is in place"
|
||||
echo "Ignored for now - until working solution is in place (#13777)"
|
||||
|
||||
deb_signing:
|
||||
name: Sign source and binary packages
|
||||
|
|
@ -211,8 +211,8 @@ jobs:
|
|||
- name: Sign packages
|
||||
uses: sillsdev/gha-deb-signing@a38dbde6bc9afabede5e07609105acc83c760ad4 # v0.6
|
||||
with:
|
||||
src-pkg-name: "keyman_${{ needs.sourcepackage.outputs.VERSION }}-1_source.changes"
|
||||
bin-pkg-name: "keyman_${{ needs.sourcepackage.outputs.VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+"
|
||||
src-pkg-name: "keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}-1_source.changes"
|
||||
bin-pkg-name: "keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}-1${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}+"
|
||||
artifacts-prefix: "keyman-"
|
||||
artifacts-result-name: "keyman-signedpkgs"
|
||||
gpg-signing-key: "${{ secrets.GPG_SIGNING_KEY }}"
|
||||
|
|
@ -247,7 +247,7 @@ jobs:
|
|||
echo "::endgroup::"
|
||||
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
name: keyman-signedpkgs
|
||||
|
||||
|
|
@ -293,7 +293,7 @@ jobs:
|
|||
echo "::endgroup::"
|
||||
|
||||
echo -e "::group::${COLOR_GREEN}Uploading binary packages"
|
||||
pattern="keyman_${{ needs.sourcepackage.outputs.VERSION }}.+\+(.*)[0-9]_[^.]+.changes"
|
||||
pattern="keyman_${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}.+\+(.*)[0-9]_[^.]+.changes"
|
||||
for f in *_amd64.changes; do
|
||||
if [[ $f =~ $pattern ]]; then
|
||||
dist=${BASH_REMATCH[1]}
|
||||
|
|
@ -303,20 +303,21 @@ jobs:
|
|||
echo "::endgroup::"
|
||||
|
||||
prepare_api_verification:
|
||||
name: Verify API for libkeymancore.so
|
||||
name: Prepare API verification
|
||||
needs: [sourcepackage, binary_packages_released]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download Artifacts
|
||||
uses: actions/download-artifact@6b208ae046db98c579e8a3aa621ab581ff575935 # v4.1.1
|
||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
||||
with:
|
||||
path: artifacts
|
||||
pattern: keyman-*
|
||||
merge-multiple: true
|
||||
|
||||
- name: Save environment
|
||||
run: |
|
||||
echo "VERSION=${{ needs.sourcepackage.outputs.VERSION }}" > artifacts/env
|
||||
echo "KEYMAN_VERSION=${{ needs.sourcepackage.outputs.KEYMAN_VERSION }}" > artifacts/env
|
||||
echo "PRERELEASE_TAG=${{ needs.sourcepackage.outputs.PRERELEASE_TAG }}" >> artifacts/env
|
||||
echo "GIT_SHA=${{ github.event.client_payload.buildSha }}" >> artifacts/env
|
||||
echo "GIT_BASE=${{ github.event.client_payload.baseRef }}" >> artifacts/env
|
||||
|
|
@ -326,7 +327,7 @@ jobs:
|
|||
echo "GIT_USER=${{ github.event.client_payload.user }}" >> artifacts/env
|
||||
|
||||
- name: Cache artifacts
|
||||
uses: actions/cache/save@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0
|
||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
with:
|
||||
path: |
|
||||
artifacts
|
||||
|
|
|
|||
|
|
@ -384,10 +384,11 @@ group.
|
|||
| `auto` | [search][search-auto] | For PRs only: automatically-opened PRs, e.g. opened by CI. |
|
||||
| `bug` | [search][search-bug] | For issues only: confirmed bugs or reports that are very likely to be bugs. PRs use `fix` to mark a bug fix. |
|
||||
| `change` | [search][search-change] | Minor change in functionality, but not new. |
|
||||
| `chore` | [search][search-chore] | Cleanup work, maintenance, without change in functionality. |
|
||||
| `chore` | [search][search-chore] | Cleanup work, without change in functionality. |
|
||||
| `docs` | [search][search-docs] | Relating to any type of documentation. |
|
||||
| `feat` | [search][search-feat] | Feature requests. |
|
||||
| `fix` | [search][search-fix] | For PRs only: a bug fix, corresponds to issue label `bug`. |
|
||||
| `maint` | [search][search-maint] | Maintenance work -- continuous integration, build scripts, infrastructure. |
|
||||
| `question` | [search][search-question] | Questions more than bug reports or feature requests (e.g. how do I do X). [**Usually better on the Keyman Community**](https://community.software.sil.org/c/keyman) |
|
||||
| `refactor` | [search][search-refactor] | Code reorganization and refactoring, without change in functionality. |
|
||||
| `spec` | [search][search-spec] | Issues which are specifications for a large scale feature. |
|
||||
|
|
@ -443,6 +444,7 @@ These labels also include sublabels, such as `windows/config/`.
|
|||
[search-docs]: https://github.com/keymanapp/keyman/labels/docs
|
||||
[search-feat]: https://github.com/keymanapp/keyman/labels/feat
|
||||
[search-fix]: https://github.com/keymanapp/keyman/labels/fix
|
||||
[search-maint]: https://github.com/keymanapp/keyman/labels/maint
|
||||
[search-question]: https://github.com/keymanapp/keyman/labels/question
|
||||
[search-spec]: https://github.com/keymanapp/keyman/labels/spec
|
||||
[search-style]: https://github.com/keymanapp/keyman/labels/style
|
||||
|
|
|
|||
315
HISTORY.md
315
HISTORY.md
|
|
@ -1,5 +1,277 @@
|
|||
# Keyman Version History
|
||||
|
||||
## 19.0.63 alpha 2025-06-12
|
||||
|
||||
* maint(web): add some more arguments for web release build script (#14167)
|
||||
* maint(developer): use double quotes so that variable gets replaced (#14168)
|
||||
* chore(deps): bump brace-expansion from 2.0.1 to 2.0.2 in /developer/src/server/src/win32/trayicon/addon-src (#14165)
|
||||
|
||||
## 19.0.62 alpha 2025-06-11
|
||||
|
||||
* maint(developer): export environment variables (#14157)
|
||||
* maint(web): use path when calling powershell script (#14156)
|
||||
* maint(resources): fix regression on mac triggering builds (#14155)
|
||||
|
||||
## 19.0.61 alpha 2025-06-10
|
||||
|
||||
* chore(developer): extend unit test timeout for getLastGitCommitDate (#14147)
|
||||
* fix(common): Use zip.inc.sh for common test keyboards (#14145)
|
||||
* maint(developer): add docker image to build developer node modules (#14058)
|
||||
|
||||
## 19.0.60 alpha 2025-06-06
|
||||
|
||||
* maint(developer): allow to build locally on Linux (#14059)
|
||||
|
||||
## 19.0.59 alpha 2025-06-05
|
||||
|
||||
* chore(deps): bump multer from 2.0.0 to 2.0.1 (#14127)
|
||||
* maint(web): fix working directory for zipping artifacts for Web release (#14128)
|
||||
* chore(linux): Update debian changelog (#14119)
|
||||
* maint(web): improve TC build script for Keyman Web/Test (#14051)
|
||||
* maint(developer): move TC configuration for Keyman Developer to scripts (#14052)
|
||||
|
||||
## 19.0.58 alpha 2025-06-04
|
||||
|
||||
* feat(developer): update strs compiler to collect context (#14064)
|
||||
* feat(developer): line numbers for UsetCompiler, others (#14090)
|
||||
* feat(developer): remaining line numbers for tran/bksp compilers (#14097)
|
||||
* feat(developer): ldml: update errors for unparseable reorder (#14098)
|
||||
* chore(developer): ldml: messages cleanup (#14100)
|
||||
* maint(android): Create "archive" builder step to prep release artifacts (#14093)
|
||||
* maint(web): move TC configuration for Web to script (#14049)
|
||||
|
||||
## 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)
|
||||
* docs(developer): add compiler message internal documentation (#14025)
|
||||
* feat(developer): add line numbers to vars compiler (#14036)
|
||||
|
||||
## 19.0.48 alpha 2025-05-23
|
||||
|
||||
* maint(web): upgrade Sentry to 8.55.0 (#14030)
|
||||
|
||||
## 19.0.47 alpha 2025-05-22
|
||||
|
||||
* fix(windows): only breadcrumb unexpected state on first run (#13984)
|
||||
* maint: change builder_completion script to a .inc.sh (#14007)
|
||||
* fix(windows): skip sending handle kmshell events to upgrade state machine for keyboard install modes (#13989)
|
||||
* fix(windows): fix sentry error message reporting (#14027)
|
||||
|
||||
## 19.0.46 alpha 2025-05-21
|
||||
|
||||
* docs(common): update README with number of languages and keyboards (#14023)
|
||||
|
||||
## 19.0.45 alpha 2025-05-21
|
||||
|
||||
* maint(linux): use correct syntax to define local variables in script (#14021)
|
||||
* maint(developer): update multer to v2.0.0 (#14012)
|
||||
|
||||
## 19.0.44 alpha 2025-05-21
|
||||
|
||||
* feat(android): log active keyboard, model IDs with reported errors (#13983)
|
||||
* fix(windows): rename Enter and Exit methods TState (#13920)
|
||||
* maint: update macOS environment setup, related documentation (#13979)
|
||||
* chore(deps): bump multer from 1.4.5-lts.1 to 2.0.0 (#13982)
|
||||
* feat(ios): log active keyboard, model IDs with reported errors (#13987)
|
||||
* maint(linux): add quilt to release build configuration (#13986)
|
||||
* chore(ios): update FirstVoices AppIcon (#13976)
|
||||
* docs(developer): add security note to Upload documentation (#14015)
|
||||
* fix(android): Check network access before trying to download keyboard (#13978)
|
||||
|
||||
## 19.0.43 alpha 2025-05-20
|
||||
|
||||
* chore(ios): FirstVoices certificate update (#13991)
|
||||
* fix(linux): configurable path in `.service` file when using basu (#13980)
|
||||
|
||||
## 19.0.42 alpha 2025-05-20
|
||||
|
||||
* maint(linux): add `--no-werror` option to not report warnings as errors (#13988)
|
||||
|
||||
## 19.0.41 alpha 2025-05-19
|
||||
|
||||
* fix(web): inspectable test-script mode should not be auto-enabled in local-env tests (#13895)
|
||||
* maint(common): evaluate and apply npm audit fix (without forced changes) (#13897)
|
||||
* maint: update mocha (#13899)
|
||||
* maint: update @actions/github + @octokit references (#13900)
|
||||
* maint(common): update fast-json-patch to ^3.1.1 (#13901)
|
||||
* change(web): cancel flicks when both returning to and releasing at original tap location (#13683)
|
||||
* fix(web): empty layout fontSize property should default to 1em (#13959)
|
||||
* maint(linux): fix release build to run all tests except integration tests (#13968)
|
||||
|
||||
## 19.0.40 alpha 2025-05-17
|
||||
|
||||
* maint(common): Add zip.inc.sh for cross-platform zipping (#13812)
|
||||
|
||||
## 19.0.39 alpha 2025-05-16
|
||||
|
||||
* chore(mac,windows): Update Crowdin strings for Italian (#13871)
|
||||
* refactor(web): rename generic type variables (#13930)
|
||||
* maint(linux): properly fallback to `VERSION` in deb-packaging (#13961)
|
||||
* maint(linux): update gha-ubuntu-packaging dependency (#13964)
|
||||
* fix(linux): use correct header guard define in OrderedOutputDevice.h (#13962)
|
||||
* maint(linux): don't include `dockerbuild` log files (#13965)
|
||||
* maint(linux): update GHA dependencies for Linux package builds (#13966)
|
||||
|
||||
## 19.0.38 alpha 2025-05-15
|
||||
|
||||
* chore(android): Update FirstVoices launcher icon and red theme (#13907)
|
||||
* feat(developer): add more line number issues for KeysCompiler (#13915)
|
||||
* feat(developer): add line number for MetaCompiler (#13916)
|
||||
* maint(linux): address comments from Debian package review (#13904)
|
||||
* maint(linux): add Questing to launchpad package builds (#13910)
|
||||
* maint(linux): fix logging in `deb-packaging.sh` (#13912)
|
||||
* maint(linux): remove temporary files created during dependency installation (#13913)
|
||||
* maint(common): move TC build configs for Linux to scripts (#13754)
|
||||
* fix(developer): remove ngrok binary at build time (#13847)
|
||||
* chore(linux): Update debian changelog (#13889)
|
||||
* refactor(web): rename `HardKeyboard` to `HardKeyboardBase` (#13929)
|
||||
* refactor(web): rename base `KeymanEngine` to `KeymanEngineBase` (#13942)
|
||||
|
||||
## 19.0.37 alpha 2025-05-14
|
||||
|
||||
* maint(linux): remove generated file that got accidentally committed (#13902)
|
||||
* maint(linux): remove check for patched ibus from `meson.build` (#13909)
|
||||
* maint(common): report builder errors to stderr (#13882)
|
||||
|
||||
## 19.0.36 alpha 2025-05-13
|
||||
|
||||
* feat(developer): line numbers for XML parsing and kmc-ldml (#13375)
|
||||
* maint(android): Update to Java 21 and Gradle dependencies (#13858)
|
||||
* maint(linux): improve getting PR# in `upload-to-debian.sh` (#13890)
|
||||
* fix(linux): fallback to `VERSION` if `KEYMAN_VERSION` isn't set during package build (#13891)
|
||||
* maint(common): move and document `builder_is_ci_*` functions (#13892)
|
||||
* maint(android): use new variable name `KEYMAN_TIER` (#13898)
|
||||
* fix(mac): handle PackageInfo section in kmp.inf file (#13876)
|
||||
|
||||
## 19.0.35 alpha 2025-05-12
|
||||
|
||||
* maint(common): add two more functions to detect if running on CI (#13869)
|
||||
* maint(common): use unique names for Keyman version variables (#13854)
|
||||
* maint(common): use new `builder_is_running_on_*` functions (#13875)
|
||||
* maint(common): remove obsolete code from docker-images `run.sh` (#13879)
|
||||
* chore(mac): remove obsolete key processing code (#13848)
|
||||
|
||||
## 19.0.34 alpha 2025-05-08
|
||||
|
||||
* docs(common): update `builder_echo` documentation for TC blocks (#13855)
|
||||
* fix(windows): add breadcrumb to download failures (#13826)
|
||||
* fix(web): sustain active batched-layout mode when keyboard is switched (#13860)
|
||||
* maint(common): remove obsolete code from docker-images `build.sh` (#13861)
|
||||
* maint(common): output start/end messages if not running on TC (#13868)
|
||||
|
||||
## 19.0.33 alpha 2025-05-07
|
||||
|
||||
* maint(common): automatically set --debug option for local-environment builder-script uses (#13827)
|
||||
* docs(web): add a Web engine keystroke lifecycle internal doc (#13759)
|
||||
* maint(common): use function to detect if running on TC (#13844)
|
||||
* docs(common): document new `builder_is_running_on_teamcity` function (#13852)
|
||||
|
||||
## 19.0.32 alpha 2025-05-06
|
||||
|
||||
* change(web): replace USVString with string in predictive-text engine (#13785)
|
||||
* chore(web): replace --ci flag with builder_is_ci_build (#13819)
|
||||
* maint(web): simplify worker-main test.sh (#13820)
|
||||
* change(web): lint kmwstring web-utility (#13800)
|
||||
* fix(web): fix extraction of gesture-engine fixture used in DOM-based unit tests (#13805)
|
||||
* fix(web): add robustness for bad touch-layout fontsize specs (#13774)
|
||||
* fix(web): fix var declaration affected by linter strictness + length-style fix (#13839)
|
||||
* chore(ios): Update inuktitut_pirurvik to 1.4.1 (#13840)
|
||||
* chore(common): override check when running on TC (#13779)
|
||||
* change(linux): add ICU dependency needed to build on Arch Linux (#13842)
|
||||
* maint(common): add support for TC blocks (#13810)
|
||||
|
||||
## 19.0.31 alpha 2025-05-05
|
||||
|
||||
* fix(mac): add version metadata after building tests (#13818)
|
||||
* maint(common): remove `eslint:recommended` configuration for .ts (#13825)
|
||||
* maint(common): Update write_download_info() to be cross-platform (#13813)
|
||||
* change(ios): limit reports about devices without predefined scaling entries (#13806)
|
||||
* change(web): lint engine/common/web-utils (#13797)
|
||||
* chore(web): replace kmwFromCharCode with its ES6 equivalent (#13716)
|
||||
* chore(web): add unit tests for SMP-aware string-handling helper methods (#13719)
|
||||
* refactor(web): refactor string-helper library to eliminate its module side-effect (#13726)
|
||||
|
||||
## 19.0.30 alpha 2025-05-02
|
||||
|
||||
* chore(android): Use `builder_is_ci_build` rather than `--ci` flag (#13772)
|
||||
* docs: add `--deps` flags and move builder.md into /docs (#13815)
|
||||
* chore(web): improve clean actions (#13804)
|
||||
* fix(ios): restore keyboard-scale settings for older, still-supported iOS devices (#13773)
|
||||
* maint(web): merge LMLayer CI build config with standard Web CI build config (#13807)
|
||||
* maint(core): batch build kmx in tests for performance (#13795)
|
||||
* maint(common): cleanup meson versioning (#13796)
|
||||
* maint(common): test imported resource versions (#13803)
|
||||
|
||||
## 19.0.29 alpha 2025-04-30
|
||||
|
||||
* change(web): enable linting for Keyman Engine for Web modules (#13787)
|
||||
* fix(web): reconnect unit tests for low-level web utility classes/methods (#13714)
|
||||
* fix(android): Change Alert Dialog instead of Toast if package missing languages (#13786)
|
||||
* maint(common): remove platform advocates from CODEOWNERS (#13801)
|
||||
* chore(common): allow to run tests in virtual environment (#13793)
|
||||
* chore: increase linter strictness (#13788)
|
||||
|
||||
## 19.0.28 alpha 2025-04-29
|
||||
|
||||
* chore(common): Update dependencies in crowdin GitHub action (#13775)
|
||||
* chore: additional tweaks to scopes/labels (#13765)
|
||||
* chore(common): rename `chore` GH label to `maint` (#13780)
|
||||
* change(linux): add link to issue (#13778)
|
||||
|
||||
## 19.0.27 alpha 2025-04-28
|
||||
|
||||
* chore: update labels for v19 (#13744)
|
||||
|
||||
## 19.0.26 alpha 2025-04-24
|
||||
|
||||
* chore: merge beta version 18.0.235 history to master (#13735)
|
||||
* chore(linux): Update debian changelog (#13738)
|
||||
* chore: comment out CODEOWNERS (#13739)
|
||||
* chore(linux): add a stub unit test to allow successful coverage reporting (#13740)
|
||||
|
||||
## 19.0.25 alpha 2025-04-22
|
||||
|
||||
* chore: merge beta to master (pre-release prep) (#13730)
|
||||
|
|
@ -113,6 +385,49 @@
|
|||
* 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)
|
||||
* chore(linux): add a stub unit test to allow successful coverage reporting (#13741)
|
||||
* fix(android): Use Alert Dialog if keyboard pkg missing languages (#13802)
|
||||
* maint(web): merge LMLayer CI build config with standard Web CI build config (#13808)
|
||||
* fix(ios): restore keyboard-scale settings for older, still-supported iOS devices (#13817)
|
||||
* change(ios): limit reports about devices without predefined scaling entries (#13828)
|
||||
* chore(ios): Update inuktitut_pirurvik to 1.4.1 (#13841)
|
||||
* fix(web): add robustness for bad touch-layout fontsize specs (#13838)
|
||||
* change(linux): add ICU dependency needed to build on Arch Linux (#13843)
|
||||
* fix(mac): add version metadata after building tests (#13853)
|
||||
* fix(web): sustain active batched-layout mode when keyboard is switched (#13863)
|
||||
* fix(developer): remove ngrok binary at build time (#13873)
|
||||
* maint(common): apply new codeowners from master (#13874)
|
||||
* chore(mac,windows): Update Crowdin strings for Italian (#13872)
|
||||
|
||||
## 18.0.234 beta 2025-04-22
|
||||
|
||||
* fix(android): Require RELEASE_OEM env vars to build FirstVoices Android (#13725)
|
||||
|
|
|
|||
|
|
@ -1,16 +1,14 @@
|
|||
# License
|
||||
|
||||
Copyright (c) 2017-2024 SIL Global. All rights reserved.
|
||||
Copyright (c) 2017-2025 SIL Global. All rights reserved.
|
||||
|
||||
Licensed under the MIT License.
|
||||
|
||||
[Keyman for Linux](./linux) is licensed under the MIT License apart from [ibus-kmfl](./linux/ibus-kmfl) which is licensed under the GNU General Public License as published by the Free Software Foundation; either [version 2](./linux/ibus-kmfl/COPYING) of the License, or (at your option) any later version.
|
||||
|
||||
---
|
||||
|
||||
The MIT License
|
||||
|
||||
Copyright (c) 2017-2024 SIL Global
|
||||
Copyright (c) 2017-2025 SIL Global
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
|
@ -28,4 +26,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
||||
|
|
|
|||
|
|
@ -1,6 +1,12 @@
|
|||

|
||||
|
||||
[Keyman](https://keyman.com) makes it possible for you to type in any language on Windows, macOS, Linux, iPhone, iPad, Android tablets and phones, and even instantly in your web browser. Create keyboard layouts with Keyman Developer and share them with the community in the [keyboards repository](https://github.com/keymanapp/keyboards). The [Keyman Community](https://community.software.sil.org/c/keyman) have already contributed keyboard layouts for over 2,000 languages!
|
||||
[Keyman](https://keyman.com) makes it possible for you to type in any language
|
||||
on Windows, macOS, Linux, iPhone, iPad, Android tablets and phones, and even
|
||||
instantly in your web browser. Create keyboard layouts with Keyman Developer and
|
||||
share them with the community in the [keyboards
|
||||
repository](https://github.com/keymanapp/keyboards). The [Keyman
|
||||
Community](https://community.software.sil.org/c/keyman) have so far contributed
|
||||
well over 1,000 keyboard layouts for over 2,500 languages!
|
||||
|
||||
# License
|
||||
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
19.0.26
|
||||
19.0.64
|
||||
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/
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ function generateReleaseNotes() {
|
|||
# Copy release notes for Gradle Play Publisher to upload
|
||||
# Reference: https://github.com/Triple-T/gradle-play-publisher#uploading-release-notes
|
||||
#
|
||||
local PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/$TIER.txt"
|
||||
if [ $TIER = "stable" ]; then
|
||||
local PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/${KEYMAN_TIER}.txt"
|
||||
if [ $KEYMAN_TIER = "stable" ]; then
|
||||
PLAY_RELEASE_NOTES="$KEYMAN_ROOT/android/KMAPro/kMAPro/src/main/play/release-notes/en-US/default.txt"
|
||||
fi
|
||||
builder_heading "Generating Play Store release notes to $PLAY_RELEASE_NOTES"
|
||||
|
|
|
|||
|
|
@ -8,10 +8,10 @@ buildscript {
|
|||
}
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
// sentry-android-gradle-plugin 2.1.5+ requires AGP 7.0
|
||||
classpath 'io.sentry:sentry-android-gradle-plugin:4.6.0'
|
||||
classpath 'name.remal:gradle-plugins:1.5.0'
|
||||
classpath 'name.remal:gradle-plugins:1.9.2'
|
||||
|
||||
// From jcenter() which was deprecated August 2024
|
||||
// https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-help.inc.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-download-resources.sh"
|
||||
|
||||
|
|
@ -28,7 +29,6 @@ builder_describe "Builds Keyman for Android app." \
|
|||
"build" \
|
||||
"test Runs lint and unit tests." \
|
||||
"publish Publishes symbols to Sentry and the APK to the Play Store." \
|
||||
"--ci Don't start the Gradle daemon. For CI" \
|
||||
"--upload-sentry Upload to sentry"
|
||||
|
||||
# parse before describe_outputs to check debug flags
|
||||
|
|
@ -37,32 +37,32 @@ builder_parse "$@"
|
|||
if builder_is_debug_build; then
|
||||
builder_heading "### Debug config ####"
|
||||
CONFIG="debug"
|
||||
BUILD_FLAGS="assembleDebug -x lint -x test"
|
||||
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
|
||||
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
|
||||
fi
|
||||
|
||||
builder_describe_outputs \
|
||||
configure /android/KMAPro/kMAPro/libs/keyman-engine.aar \
|
||||
build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${VERSION}.apk
|
||||
build /android/KMAPro/kMAPro/build/outputs/apk/$CONFIG/keyman-${KEYMAN_VERSION}.apk
|
||||
|
||||
#### Build
|
||||
|
||||
|
||||
# Parse args
|
||||
|
||||
if builder_has_option --ci; then
|
||||
if builder_is_ci_build; then
|
||||
DAEMON_FLAG=--no-daemon
|
||||
fi
|
||||
|
||||
#### Build action definitions ####
|
||||
|
||||
function makeLocalSentryRelease() {
|
||||
echo "Making a Sentry release for tag $VERSION_GIT_TAG"
|
||||
echo "Making a Sentry release for tag $KEYMAN_VERSION_GIT_TAG"
|
||||
sentry-cli upload-dif -p keyman-android --include-sources
|
||||
sentry-cli releases -p keyman-android files $VERSION_GIT_TAG upload-sourcemaps ./
|
||||
sentry-cli releases -p keyman-android files $KEYMAN_VERSION_GIT_TAG upload-sourcemaps ./
|
||||
|
||||
echo "Finalizing release tag $VERSION_GIT_TAG"
|
||||
sentry-cli releases finalize "$VERSION_GIT_TAG"
|
||||
echo "Finalizing release tag $KEYMAN_VERSION_GIT_TAG"
|
||||
sentry-cli releases finalize "$KEYMAN_VERSION_GIT_TAG"
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,7 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -20,6 +20,15 @@ android {
|
|||
noCompress "kmp"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.tavultesoft.kmapro"
|
||||
minSdkVersion 21
|
||||
|
|
@ -29,11 +38,11 @@ android {
|
|||
//dumpProperties(project) // Use this to dump all external properties for debugging TeamCity integration
|
||||
//println "===END DUMP==="
|
||||
|
||||
// VERSION strings from version.gradle
|
||||
versionCode VERSION_CODE as Integer
|
||||
versionName VERSION_NAME
|
||||
buildConfigField 'String', 'VERSION_ENVIRONMENT', '"'+VERSION_ENVIRONMENT+'"'
|
||||
buildConfigField 'String', 'VERSION_GIT_TAG', '"'+VERSION_GIT_TAG+'"'
|
||||
// KEYMAN_VERSION strings from version.gradle
|
||||
versionCode KEYMAN_VERSION_CODE as Integer
|
||||
versionName KEYMAN_VERSION_NAME
|
||||
buildConfigField 'String', 'KEYMAN_VERSION_ENVIRONMENT', '"'+KEYMAN_VERSION_ENVIRONMENT+'"'
|
||||
buildConfigField 'String', 'KEYMAN_VERSION_GIT_TAG', '"'+KEYMAN_VERSION_GIT_TAG+'"'
|
||||
}
|
||||
|
||||
String env_release_store_file = System.getenv("release_store_file")
|
||||
|
|
@ -77,7 +86,7 @@ android {
|
|||
// - PR builds also have "-test-1234" suffix (where 1234 is PR#)
|
||||
// Gradle sync will nullify these values, so have a fallback
|
||||
String appSuffix = ''
|
||||
String tag = VERSION_TAG ? VERSION_TAG : '-' + tier;
|
||||
String tag = KEYMAN_VERSION_TAG ? KEYMAN_VERSION_TAG : '-' + tier;
|
||||
if (tag.startsWithAny("-beta", "beta")) {
|
||||
appSuffix = ' Beta';
|
||||
} else if (tag.startsWithAny("-alpha", "alpha")) {
|
||||
|
|
@ -88,16 +97,16 @@ android {
|
|||
// -test-PR_NUMBER
|
||||
appSuffix = appSuffix + match.group(1)
|
||||
} else {
|
||||
String environment = VERSION_ENVIRONMENT ? VERSION_ENVIRONMENT : 'local';
|
||||
String environment = KEYMAN_VERSION_ENVIRONMENT ? KEYMAN_VERSION_ENVIRONMENT : 'local';
|
||||
if (environment.matches("local")) {
|
||||
appSuffix = appSuffix + '-' + environment;
|
||||
}
|
||||
}
|
||||
variant.resValue "string", "app_name", 'Keyman' + appSuffix;
|
||||
|
||||
// Adjust output name to "keyman-${VERSION_MD}.apk"
|
||||
// Adjust output name to "keyman-${KEYMAN_VERSION_MD}.apk"
|
||||
variant.outputs.all {
|
||||
outputFileName = "keyman-" + VERSION_MD + ".apk"
|
||||
outputFileName = "keyman-" + KEYMAN_VERSION_MD + ".apk"
|
||||
}
|
||||
}
|
||||
lintOptions {
|
||||
|
|
@ -156,7 +165,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'com.stepstone.stepper:material-stepper:4.3.1'
|
||||
api(name: 'keyman-engine', ext: 'aar')
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ public class SystemKeyboard extends InputMethodService implements OnKeyboardEven
|
|||
Log.d(TAG, "Initializing Sentry");
|
||||
SentryAndroid.init(getApplicationContext(), options -> {
|
||||
options.setEnableAutoSessionTracking(false);
|
||||
options.setRelease(com.tavultesoft.kmapro.BuildConfig.VERSION_GIT_TAG);
|
||||
options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.VERSION_ENVIRONMENT);
|
||||
options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG);
|
||||
options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT);
|
||||
});
|
||||
}
|
||||
if (BuildConfig.DEBUG) {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,14 @@ public class KMPBrowserActivity extends BaseActivity {
|
|||
Intent intent = new Intent(context, MainActivity.class);
|
||||
intent.setData(downloadURI);
|
||||
KMLog.LogBreadcrumb(TAG, "Successful keyboard search now triggering package install", true);
|
||||
startActivity(intent);
|
||||
|
||||
// Verify network access before trying to download
|
||||
if (KMManager.hasConnection(context)) {
|
||||
startActivity(intent);
|
||||
} else {
|
||||
Toast.makeText(context, getString(R.string.no_internet_connection), Toast.LENGTH_LONG).show();
|
||||
return true;
|
||||
}
|
||||
|
||||
// Finish activity
|
||||
finish();
|
||||
|
|
|
|||
|
|
@ -144,8 +144,8 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
if (KMManager.getMaySendCrashReport()) {
|
||||
SentryAndroid.init(context, options -> {
|
||||
options.setEnableAutoSessionTracking(false);
|
||||
options.setRelease(com.tavultesoft.kmapro.BuildConfig.VERSION_GIT_TAG);
|
||||
options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.VERSION_ENVIRONMENT);
|
||||
options.setRelease(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_GIT_TAG);
|
||||
options.setEnvironment(com.tavultesoft.kmapro.BuildConfig.KEYMAN_VERSION_ENVIRONMENT);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -440,41 +440,41 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
switch (item.getItemId()) {
|
||||
case R.id.action_info:
|
||||
showInfo();
|
||||
return true;
|
||||
case R.id.action_share:
|
||||
showShareDialog();
|
||||
return true;
|
||||
// Android Gradle 8.0 no longer declares resources final, so can't use switch statement here
|
||||
if (item.getItemId() == R.id.action_info) {
|
||||
showInfo();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_share) {
|
||||
showShareDialog();
|
||||
return true;
|
||||
/* Disable Web Browser to investigate Google sign-in
|
||||
case R.id.action_web:
|
||||
} else if ((item.getItemId() == R.id.action_web) {
|
||||
showWebBrowser();
|
||||
return true;*/
|
||||
case R.id.action_text_size:
|
||||
showTextSizeDialog();
|
||||
return true;
|
||||
case R.id.action_clear_text:
|
||||
showClearTextDialog();
|
||||
return true;
|
||||
case R.id.action_get_started:
|
||||
showGetStarted();
|
||||
return true;
|
||||
case R.id.action_settings:
|
||||
showSettings();
|
||||
return true;
|
||||
case R.id.action_update_keyboards:
|
||||
KMManager.getUpdateTool().executeOpenUpdates();
|
||||
// Dismiss icon
|
||||
updateUpdateCountIndicator(0);
|
||||
final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards);
|
||||
if (_keyboardupdate != null && _keyboardupdate.isVisible()) {
|
||||
_keyboardupdate.setVisible(false);
|
||||
}
|
||||
} else if (item.getItemId() == R.id.action_text_size) {
|
||||
showTextSizeDialog();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_clear_text){
|
||||
showClearTextDialog();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_get_started) {
|
||||
showGetStarted();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_settings) {
|
||||
showSettings();
|
||||
return true;
|
||||
} else if (item.getItemId() == R.id.action_update_keyboards) {
|
||||
KMManager.getUpdateTool().executeOpenUpdates();
|
||||
// Dismiss icon
|
||||
updateUpdateCountIndicator(0);
|
||||
final MenuItem _keyboardupdate = menu.findItem(R.id.action_update_keyboards);
|
||||
if (_keyboardupdate != null && _keyboardupdate.isVisible()) {
|
||||
_keyboardupdate.setVisible(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
default:
|
||||
return super.onOptionsItemSelected(item);
|
||||
return true;
|
||||
} else {
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -632,7 +632,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
Intent i = new Intent(this, InfoActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
startActivity(i);
|
||||
overridePendingTransition(android.R.anim.fade_in, R.anim.hold);
|
||||
overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold);
|
||||
}
|
||||
|
||||
private void showWebBrowser() {
|
||||
|
|
@ -643,7 +643,7 @@ public class MainActivity extends BaseActivity implements OnKeyboardEventListene
|
|||
Intent i = new Intent(this, WebBrowserActivity.class);
|
||||
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
|
||||
startActivity(i);
|
||||
overridePendingTransition(android.R.anim.fade_in, R.anim.hold);
|
||||
overridePendingTransition(android.R.anim.fade_in, com.keyman.engine.R.anim.hold);
|
||||
}
|
||||
|
||||
private void showShareDialog() {
|
||||
|
|
|
|||
|
|
@ -116,12 +116,14 @@ public class PackageActivity extends AppCompatActivity implements
|
|||
kmpProcessor.getLanguageCount(pkgInfo, PackageProcessor.PP_KEYBOARDS_KEY, 0) : 0;
|
||||
|
||||
// Sanity check for keyboard packages
|
||||
// Not using showErrorToast to avoid cluttering Sentry with noise
|
||||
if (pkgTarget.equals(PackageProcessor.PP_TARGET_KEYBOARDS)) {
|
||||
if (keyboardCount == 0) {
|
||||
showErrorDialog(context, pkgId, getString(R.string.no_new_touch_keyboards_to_install));
|
||||
return;
|
||||
} else if (languageCount == 0) {
|
||||
showErrorToast(getString(R.string.no_associated_languages));
|
||||
showErrorDialog(context, pkgId, getString(R.string.no_associated_languages));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
plugins {
|
||||
id 'base'
|
||||
id 'com.android.library'
|
||||
id 'name.remal.default-plugins'
|
||||
}
|
||||
|
|
@ -6,20 +7,32 @@ plugins {
|
|||
ext.rootPath = '../../'
|
||||
apply from: "$rootPath/version.gradle"
|
||||
|
||||
base {
|
||||
// Specify library filename keyman-engine-$CONFIG.aar
|
||||
archivesName = "keyman-engine"
|
||||
}
|
||||
|
||||
android {
|
||||
compileSdk 34
|
||||
namespace "com.keyman.engine"
|
||||
|
||||
buildFeatures {
|
||||
// needed for custom BuildConfigField values in defaultConfig
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
|
||||
// VERSION_CODE and VERSION_NAME from version.gradle but Gradle removes them for libraries
|
||||
buildConfigField "String", "KEYMAN_ENGINE_VERSION_NAME", "\""+VERSION_NAME+"\""
|
||||
buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\""
|
||||
|
||||
// Sepcify library filename keyman-engine-$CONFIG.aar
|
||||
setProperty("archivesBaseName", "keyman-engine")
|
||||
// KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle but Gradle removes them for libraries
|
||||
buildConfigField "String", "KEYMAN_ENGINE_VERSION_NAME", "\""+KEYMAN_VERSION_NAME+"\""
|
||||
buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\""
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -36,10 +49,6 @@ android {
|
|||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
// Allows use of a simulated Android API for tests. (Thanks, roboelectric!)
|
||||
includeAndroidResources = true
|
||||
}
|
||||
unitTests.all {
|
||||
testLogging {
|
||||
// May also add "passed", "skipped".
|
||||
|
|
@ -55,17 +64,17 @@ android {
|
|||
|
||||
dependencies {
|
||||
// 1.6.0-rc01 needed to resolve https://issuetracker.google.com/issues/238425626
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
// material:1.7.0 will need Gradle plugin 7.1.0+
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
implementation 'commons-io:commons-io:2.13.0'
|
||||
implementation 'commons-io:commons-io:2.16.1'
|
||||
implementation 'io.sentry:sentry-android:7.8.0'
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
||||
// Robolectric
|
||||
testImplementation 'androidx.test:core:1.5.0'
|
||||
testImplementation 'androidx.test.ext:junit:1.1.5'
|
||||
testImplementation 'org.robolectric:robolectric:4.10.3'
|
||||
testImplementation 'androidx.test.ext:junit:1.2.1'
|
||||
testImplementation 'org.robolectric:robolectric:4.14.1'
|
||||
testImplementation 'androidx.test:core:1.6.1'
|
||||
|
||||
// Generate QR Codes
|
||||
implementation ('com.github.kenglxn.QRGen:android:3.0.1') {
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
<title>Keyman</title>
|
||||
<!-- DOMRect only exists as of Chrome 61... thus coming later than ES6. -->
|
||||
<script src="other-polyfills.js"></script>
|
||||
<script src="sentry.min.js"></script>
|
||||
<script src="keyman-sentry.js"></script>
|
||||
<script src="keymanweb-webview.js"></script>
|
||||
<script src="android-host.js"></script>
|
||||
|
|
|
|||
|
|
@ -362,8 +362,7 @@ public final class KMManager {
|
|||
// Keyman files
|
||||
protected static final String KMFilename_KeyboardHtml = "keyboard.html";
|
||||
protected static final String KMFilename_JSEngine = "keymanweb-webview.js";
|
||||
protected static final String KMFilename_JSSentry = "sentry.min.js";
|
||||
protected static final String KMFilename_JSSentryInit = "keyman-sentry.js";
|
||||
protected static final String KMFilename_JSSentry = "keyman-sentry.js";
|
||||
protected static final String KMFilename_AndroidHost = "android-host.js";
|
||||
protected static final String KMFilename_KmwCss = "kmwosk.css";
|
||||
protected static final String KMFilename_KmwGlobeHintCss = "globe-hint.css";
|
||||
|
|
@ -982,7 +981,6 @@ public final class KMManager {
|
|||
|
||||
copyAsset(context, KMFilename_JSEngine, "", true);
|
||||
copyAsset(context, KMFilename_JSSentry, "", true);
|
||||
copyAsset(context, KMFilename_JSSentryInit, "", true);
|
||||
copyAsset(context, KMFilename_AndroidHost, "", true);
|
||||
copyAsset(context, KMFilename_KmwCss, "", true);
|
||||
copyAsset(context, KMFilename_KmwGlobeHintCss, "", true);
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.keyman.engine.cloud;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.app.DownloadManager;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
|
|
@ -60,6 +61,7 @@ public class CloudDownloadMgr{
|
|||
* Append downloadreceiver to the main context.
|
||||
* @param aContext the context
|
||||
*/
|
||||
@SuppressLint("UnspecifiedRegisterReceiverFlag")
|
||||
public synchronized void initialize(Context aContext)
|
||||
{
|
||||
if(isInitialized)
|
||||
|
|
@ -73,6 +75,7 @@ public class CloudDownloadMgr{
|
|||
aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),
|
||||
Context.RECEIVER_EXPORTED);
|
||||
} else {
|
||||
// Needs @SuppressLint UnspecifiedRegisterReceiverFlag
|
||||
aContext.registerReceiver(completeListener, new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE));
|
||||
}
|
||||
} catch (IllegalArgumentException e) {
|
||||
|
|
|
|||
|
|
@ -4,15 +4,19 @@
|
|||
|
||||
package com.keyman.engine.util;
|
||||
|
||||
import static com.keyman.engine.KMManager.KMKey_LexicalModelID;
|
||||
|
||||
import android.util.Log;
|
||||
import android.widget.Toast;
|
||||
|
||||
import com.keyman.engine.BaseActivity;
|
||||
import com.keyman.engine.BuildConfig;
|
||||
import com.keyman.engine.KMManager;
|
||||
import com.keyman.engine.util.DependencyUtil;
|
||||
import com.keyman.engine.data.Keyboard;
|
||||
import com.keyman.engine.util.DependencyUtil.LibraryType;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import io.sentry.Breadcrumb;
|
||||
import io.sentry.Sentry;
|
||||
import io.sentry.SentryLevel;
|
||||
|
|
@ -20,19 +24,67 @@ import io.sentry.SentryLevel;
|
|||
public final class KMLog {
|
||||
private static final String TAG = "KMLog";
|
||||
|
||||
private static final String KEYBOARD_TAG = "keyboardId";
|
||||
private static final String KEYBOARD_COUNT_TAG = "installedKeyboardCount";
|
||||
private static final String MODEL_TAG = "modelId";
|
||||
private static final String LANGCODE_TAG = "languageCode";
|
||||
|
||||
// Some of the methods used to generate debug logging information can, themselves,
|
||||
// trigger errors that can also trigger the same logging. We must not get
|
||||
// caught in an infinite loop / stack-overflow; this field helps us avoid states
|
||||
// that would otherwise cause error-looping, etc.
|
||||
private static boolean isLogging = false;
|
||||
|
||||
private static void tagDebugInfo() {
|
||||
String kbdId = "";
|
||||
String lngCode = "";
|
||||
String modelId = "";
|
||||
int kbdCount = 0;
|
||||
// Do not risk raising a new error while tagging info for another error.
|
||||
try {
|
||||
// Both take a context parameter... but don't actually need or use it!
|
||||
Keyboard kbd = KMManager.getCurrentKeyboardInfo(null);
|
||||
kbdCount = KMManager.getKeyboardsList(null).size();
|
||||
if (kbd != null) {
|
||||
kbdId = kbd.getKeyboardID();
|
||||
lngCode = kbd.getLanguageCode();
|
||||
Map<String, String> modelMap = KMManager.getAssociatedLexicalModel(kbd.getLanguageID());
|
||||
if (modelMap != null) {
|
||||
modelId = modelMap.get(KMKey_LexicalModelID);
|
||||
if (modelId == null) {
|
||||
modelId = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
String msg = ex.getMessage() == null ? "" : ex.getMessage();
|
||||
Sentry.setExtra("debugLoggingError", msg);
|
||||
}
|
||||
Sentry.setExtra(KEYBOARD_TAG, kbdId);
|
||||
Sentry.setExtra(KEYBOARD_COUNT_TAG, "" + kbdCount);
|
||||
Sentry.setExtra(LANGCODE_TAG, lngCode);
|
||||
Sentry.setExtra(MODEL_TAG, modelId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility to log info and send to Sentry
|
||||
* @param tag String of the caller
|
||||
* @param msg String of the info message
|
||||
*/
|
||||
public static void LogInfo(String tag, String msg) {
|
||||
if(isLogging) {
|
||||
return;
|
||||
}
|
||||
isLogging = true;
|
||||
if (msg != null && !msg.isEmpty()) {
|
||||
Log.i(tag, msg);
|
||||
|
||||
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
|
||||
tagDebugInfo();
|
||||
Sentry.captureMessage(msg, SentryLevel.INFO);
|
||||
}
|
||||
}
|
||||
isLogging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -46,9 +98,15 @@ public final class KMLog {
|
|||
return;
|
||||
}
|
||||
|
||||
if(isLogging) {
|
||||
return;
|
||||
}
|
||||
isLogging = true;
|
||||
|
||||
Log.i(tag, msg);
|
||||
|
||||
if (!DependencyUtil.libraryExists(LibraryType.SENTRY) || !Sentry.isEnabled()) {
|
||||
isLogging = false;
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -74,7 +132,9 @@ public final class KMLog {
|
|||
crumb.setData("stacktrace", trace);
|
||||
}
|
||||
}
|
||||
tagDebugInfo();
|
||||
Sentry.addBreadcrumb(crumb);
|
||||
isLogging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -83,6 +143,10 @@ public final class KMLog {
|
|||
* @param msg String of the error message
|
||||
*/
|
||||
public static void LogError(String tag, String msg) {
|
||||
if(isLogging) {
|
||||
return;
|
||||
}
|
||||
isLogging = true;
|
||||
if (msg != null && !msg.isEmpty()) {
|
||||
Log.e(tag, msg);
|
||||
|
||||
|
|
@ -91,9 +155,11 @@ public final class KMLog {
|
|||
}
|
||||
|
||||
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
|
||||
tagDebugInfo();
|
||||
Sentry.captureMessage(msg, SentryLevel.ERROR);
|
||||
}
|
||||
}
|
||||
isLogging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -103,6 +169,10 @@ public final class KMLog {
|
|||
* @param e Throwable exception
|
||||
*/
|
||||
public static void LogException(String tag, String msg, Throwable e) {
|
||||
if(isLogging) {
|
||||
return;
|
||||
}
|
||||
isLogging = true;
|
||||
String errorMsg = "";
|
||||
if (msg != null && !msg.isEmpty()) {
|
||||
errorMsg = msg + "\n" + e;
|
||||
|
|
@ -116,9 +186,11 @@ public final class KMLog {
|
|||
}
|
||||
|
||||
if (DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
|
||||
tagDebugInfo();
|
||||
Sentry.addBreadcrumb(errorMsg);
|
||||
Sentry.captureException(e);
|
||||
}
|
||||
isLogging = false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -131,7 +203,12 @@ public final class KMLog {
|
|||
*/
|
||||
public static void LogExceptionWithData(String tag, String msg,
|
||||
String objName, Object obj, Throwable e) {
|
||||
if(isLogging) {
|
||||
return;
|
||||
}
|
||||
isLogging = true;
|
||||
if (obj != null && DependencyUtil.libraryExists(LibraryType.SENTRY) && Sentry.isEnabled()) {
|
||||
tagDebugInfo();
|
||||
String objStr = null;
|
||||
try {
|
||||
objStr = obj.toString();
|
||||
|
|
@ -141,6 +218,10 @@ public final class KMLog {
|
|||
}
|
||||
// Report the original exception
|
||||
LogException(tag, msg, e);
|
||||
// And remove the exception-specific tagged data, lest it also be
|
||||
// tracked on subsequent errors not associated with the current call.
|
||||
Sentry.removeExtra(objName);
|
||||
}
|
||||
isLogging = false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,11 +12,11 @@ public final class VersionUtils {
|
|||
public static final String TAG = "VersionUtils";
|
||||
|
||||
public static boolean isLocalBuild() {
|
||||
return BuildConfig.VERSION_ENVIRONMENT.equalsIgnoreCase("local");
|
||||
return BuildConfig.KEYMAN_VERSION_ENVIRONMENT.equalsIgnoreCase("local");
|
||||
}
|
||||
|
||||
public static boolean isTestBuild() {
|
||||
return BuildConfig.VERSION_ENVIRONMENT.equalsIgnoreCase("test");
|
||||
return BuildConfig.KEYMAN_VERSION_ENVIRONMENT.equalsIgnoreCase("test");
|
||||
}
|
||||
|
||||
// Utility for local and PR test builds - e.g. force check of keyboard updates
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
// io.sentry:sentry-android-gradle-plugin not available for library project
|
||||
classpath 'io.sentry:sentry-android:7.8.0'
|
||||
classpath 'name.remal:gradle-plugins:1.5.0'
|
||||
classpath 'name.remal:gradle-plugins:1.9.2'
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
|
||||
# ################################ Main script ################################
|
||||
|
||||
|
|
@ -28,8 +29,7 @@ builder_describe "Builds Keyman Engine for Android." \
|
|||
"configure" \
|
||||
"build" \
|
||||
"test Runs lint and unit tests." \
|
||||
":engine Builds Engine" \
|
||||
"--ci Don't start the Gradle daemon. For CI"
|
||||
":engine Builds Engine"
|
||||
|
||||
# parse before describe_outputs to check debug flags
|
||||
builder_parse "$@"
|
||||
|
|
@ -37,7 +37,7 @@ builder_parse "$@"
|
|||
if builder_is_debug_build; then
|
||||
builder_heading "### Debug config ####"
|
||||
CONFIG="debug"
|
||||
BUILD_FLAGS="assembleDebug -x lint -x test"
|
||||
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
|
||||
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
|
||||
JUNIT_RESULTS="##teamcity[importData type='junit' path='keyman\android\KMEA\app\build\test-results\testDebugUnitTest\']"
|
||||
fi
|
||||
|
|
@ -51,7 +51,7 @@ builder_describe_outputs \
|
|||
# Parse args
|
||||
|
||||
DAEMON_FLAG=
|
||||
if builder_has_option --ci; then
|
||||
if builder_is_ci_build; then
|
||||
DAEMON_FLAG=--no-daemon
|
||||
fi
|
||||
|
||||
|
|
@ -82,8 +82,6 @@ if builder_start_action build:engine; then
|
|||
cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/kmwosk.css" "$ENGINE_ASSETS/kmwosk.css"
|
||||
cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/globe-hint.css" "$ENGINE_ASSETS/globe-hint.css"
|
||||
cp "$KEYMAN_WEB_ROOT/build/app/resources/osk/keymanweb-osk.ttf" "$ENGINE_ASSETS/keymanweb-osk.ttf"
|
||||
|
||||
cp "$KEYMAN_ROOT/node_modules/@sentry/browser/build/bundle.min.js" "$ENGINE_ASSETS/sentry.min.js"
|
||||
cp "$KEYMAN_ROOT/web/src/engine/sentry-manager/build/lib/index.js" "$ENGINE_ASSETS/keyman-sentry.js"
|
||||
|
||||
echo "Copying es6-shim polyfill"
|
||||
|
|
@ -98,7 +96,7 @@ fi
|
|||
|
||||
if builder_start_action test:engine; then
|
||||
|
||||
if builder_has_option --ci; then
|
||||
if builder_is_ci_build; then
|
||||
# Report JUnit test results to CI
|
||||
echo "$JUNIT_RESULTS"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
#Wed Apr 30 11:17:37 ICT 2025
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-bin.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-bin.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -141,6 +141,10 @@ android {
|
|||
aaptOptions {
|
||||
noCompress "kmp"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
```
|
||||
5. After the `android {}` object, include the following:
|
||||
````gradle
|
||||
|
|
@ -154,7 +158,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api (name:'keyman-engine', ext:'aar')
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api(name: 'keyman-engine', ext: 'aar')
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
|
||||
################################ Main script ################################
|
||||
|
||||
|
|
@ -22,8 +23,7 @@ builder_describe "Build KMSample1 app for Android." \
|
|||
"configure" \
|
||||
"build" \
|
||||
"test" \
|
||||
":app KMSample1" \
|
||||
"--ci Don't start the Gradle daemon. Use for CI"
|
||||
":app KMSample1"
|
||||
|
||||
# parse before describe_outputs to check debug flags
|
||||
builder_parse "$@"
|
||||
|
|
@ -46,7 +46,7 @@ builder_describe_outputs \
|
|||
|
||||
# Parse args
|
||||
|
||||
if builder_has_option --ci; then
|
||||
if builder_is_ci_build; then
|
||||
SAMPLE_FLAGS="$SAMPLE_FLAGS -no-daemon"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -18,4 +18,8 @@
|
|||
# org.gradle.parallel=true
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api (name:'keyman-engine', ext:'aar')
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.2'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
|
||||
################################ Main script ################################
|
||||
|
||||
|
|
@ -22,8 +23,7 @@ builder_describe "Build KMSample2 app for Android." \
|
|||
"configure" \
|
||||
"build" \
|
||||
"test" \
|
||||
":app KMSample2" \
|
||||
"--ci Don't start the Gradle daemon. Use for CI"
|
||||
":app KMSample2"
|
||||
|
||||
# parse before describe_outputs to check debug flags
|
||||
builder_parse "$@"
|
||||
|
|
@ -47,7 +47,7 @@ builder_describe_outputs \
|
|||
|
||||
# Parse args
|
||||
|
||||
if builder_has_option --ci; then
|
||||
if builder_is_ci_build; then
|
||||
SAMPLE_FLAGS="$SAMPLE_FLAGS -no-daemon"
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -18,4 +18,8 @@
|
|||
# org.gradle.parallel=true
|
||||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
|
|
|||
|
|
@ -14,15 +14,19 @@ android {
|
|||
noCompress "kmp"
|
||||
}
|
||||
|
||||
buildFeatures {
|
||||
buildConfig = true
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.keyman.android.tests.keyboardHarness"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
|
||||
// VERSION_CODE and VERSION_NAME from version.gradle
|
||||
versionCode VERSION_CODE as Integer
|
||||
versionName VERSION_NAME
|
||||
buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\""
|
||||
// KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle
|
||||
versionCode KEYMAN_VERSION_CODE as Integer
|
||||
versionName KEYMAN_VERSION_NAME
|
||||
buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\""
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
|
|
@ -42,7 +46,7 @@ repositories {
|
|||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||
implementation 'com.google.android.material:material:1.12.0'
|
||||
api (name:'keyman-engine', ext:'aar')
|
||||
implementation 'androidx.preference:preference:1.2.1'
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:7.4.0'
|
||||
classpath 'com.android.tools.build:gradle:8.9.2'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
|
||||
################################ Main script ################################
|
||||
|
||||
|
|
@ -22,8 +23,7 @@ builder_describe "Build KeyboardHarness test app for Android." \
|
|||
"configure" \
|
||||
"build" \
|
||||
"test" \
|
||||
":app KeyboardHarness" \
|
||||
"--ci Don't start the Gradle daemon. Use for CI"
|
||||
":app KeyboardHarness"
|
||||
|
||||
# parse before describe outputs to check debug flags
|
||||
builder_parse "$@"
|
||||
|
|
@ -33,7 +33,7 @@ ARTIFACT="app-release-unsigned.apk"
|
|||
if builder_is_debug_build; then
|
||||
builder_heading "### Debug config ####"
|
||||
CONFIG="debug"
|
||||
BUILD_FLAGS="assembleDebug -x lint -x test"
|
||||
BUILD_FLAGS="assembleDebug -x lintDebug -x testDebug"
|
||||
TEST_FLAGS="-x assembleDebug lintDebug testDebug"
|
||||
ARTIFACT="app-$CONFIG.apk"
|
||||
fi
|
||||
|
|
@ -55,7 +55,7 @@ SHLVL=0
|
|||
# Parse args
|
||||
|
||||
# Build flags that apply to all targets
|
||||
if builder_has_option --ci; then
|
||||
if builder_is_ci_build; then
|
||||
BUILD_FLAGS="$BUILD_FLAGS -no-daemon"
|
||||
TEST_FLAGS="$TEST_FLAGS -no-daemon"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -19,3 +19,6 @@
|
|||
android.enableJetifier=true
|
||||
android.useAndroidX=true
|
||||
org.gradle.jvmargs=-Xmx1024m
|
||||
|
||||
# AGP 8.0 generates R classes for resources defined in the current module only.
|
||||
android.nonTransitiveRClass=false
|
||||
|
|
|
|||
|
|
@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
|
|||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
|
|
|
|||
|
|
@ -12,10 +12,10 @@ android {
|
|||
minSdkVersion 21
|
||||
targetSdkVersion 34
|
||||
|
||||
// VERSION_CODE and VERSION_NAME from version.gradle
|
||||
versionCode VERSION_CODE as Integer
|
||||
versionName VERSION_NAME
|
||||
buildConfigField "String", "VERSION_ENVIRONMENT", "\""+VERSION_ENVIRONMENT+"\""
|
||||
// KEYMAN_VERSION_CODE and KEYMAN_VERSION_NAME from version.gradle
|
||||
versionCode KEYMAN_VERSION_CODE as Integer
|
||||
versionName KEYMAN_VERSION_NAME
|
||||
buildConfigField "String", "KEYMAN_VERSION_ENVIRONMENT", "\""+KEYMAN_VERSION_ENVIRONMENT+"\""
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.4-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.12-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
|
||||
. "$KEYMAN_ROOT/resources/build/zip.inc.sh"
|
||||
|
||||
################################ Main script ################################
|
||||
|
||||
|
|
@ -36,7 +38,8 @@ builder_describe \
|
|||
build \
|
||||
test \
|
||||
"publish Publishes symbols to Sentry and the APKs to the Play Store." \
|
||||
--ci+ \
|
||||
"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" \
|
||||
":app=KMAPro Keyman for Android" \
|
||||
|
|
@ -69,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
|
||||
|
|
|
|||
|
|
@ -7,9 +7,9 @@
|
|||
//
|
||||
|
||||
def getVersionCode = { ->
|
||||
String env_version_major = System.getenv("VERSION_MAJOR")
|
||||
String env_version_minor = System.getenv("VERSION_MINOR")
|
||||
String env_version_patch = System.getenv("VERSION_PATCH")
|
||||
String env_version_major = System.getenv("KEYMAN_VERSION_MAJOR")
|
||||
String env_version_minor = System.getenv("KEYMAN_VERSION_MINOR")
|
||||
String env_version_patch = System.getenv("KEYMAN_VERSION_PATCH")
|
||||
if(env_version_patch != null && env_version_minor != null && env_version_patch != null) {
|
||||
// Version code is, for 17.1.33: 1810033. This supports up to 9999
|
||||
// builds for a given major.minor version. We only support up to 9 minor
|
||||
|
|
@ -36,10 +36,10 @@ def getVersionCode = { ->
|
|||
}
|
||||
|
||||
def getVersionMD = { ->
|
||||
String env_version = System.getenv("VERSION")
|
||||
String env_version = System.getenv("KEYMAN_VERSION")
|
||||
if (env_version != null) {
|
||||
// If building from script, we have build number in VERSION
|
||||
println "Using build $env_version from VERSION"
|
||||
// If building from script, we have build number in KEYMAN_VERSION
|
||||
println "Using build $env_version from KEYMAN_VERSION"
|
||||
return "$env_version"
|
||||
} else {
|
||||
String version_md = file("$rootPath/../VERSION.md").text.trim()
|
||||
|
|
@ -48,15 +48,15 @@ def getVersionMD = { ->
|
|||
}
|
||||
|
||||
def getVersionName = { ->
|
||||
String env_version = System.getenv("VERSION_WITH_TAG")
|
||||
String env_version = System.getenv("KEYMAN_VERSION_WITH_TAG")
|
||||
if (env_version != null) {
|
||||
// If building from script, we have build number in VERSION_WITH_TAG
|
||||
println "Using build $env_version from VERSION_WITH_TAG"
|
||||
// If building from script, we have build number in KEYMAN_VERSION_WITH_TAG
|
||||
println "Using build $env_version from KEYMAN_VERSION_WITH_TAG"
|
||||
return "$env_version"
|
||||
} else {
|
||||
// Building probably from IDE, so let's use VERSION.md and TIER.md directly
|
||||
// This is a close match with VERSION_WITH_TAG, except that 'stable' tier
|
||||
// is not normally included with VERSION_WITH_TAG
|
||||
// This is a close match with KEYMAN_VERSION_WITH_TAG, except that 'stable' tier
|
||||
// is not normally included with KEYMAN_VERSION_WITH_TAG
|
||||
String version_md = file("$rootPath/../VERSION.md").text.trim()
|
||||
String tier_md = file("$rootPath/../TIER.md").text.trim()
|
||||
println "Using build $version_md-$tier_md-local from project VERSION.md/TIER.md"
|
||||
|
|
@ -65,10 +65,10 @@ def getVersionName = { ->
|
|||
}
|
||||
|
||||
def getVersionEnvironment = { ->
|
||||
String env_environment = System.getenv("VERSION_ENVIRONMENT")
|
||||
String env_environment = System.getenv("KEYMAN_VERSION_ENVIRONMENT")
|
||||
if (env_environment != null) {
|
||||
// If building from script, we have build number in VERSION_ENVIRONMENT
|
||||
println "Using $env_environment from VERSION_ENVIRONMENT"
|
||||
// If building from script, we have build number in KEYMAN_VERSION_ENVIRONMENT
|
||||
println "Using $env_environment from KEYMAN_VERSION_ENVIRONMENT"
|
||||
return "$env_environment"
|
||||
} else {
|
||||
// Building probably from IDE, so use "local"
|
||||
|
|
@ -78,23 +78,23 @@ def getVersionEnvironment = { ->
|
|||
}
|
||||
|
||||
def getVersionGitTag = { ->
|
||||
String env_git_tag = System.getenv("VERSION_GIT_TAG")
|
||||
String env_git_tag = System.getenv("KEYMAN_VERSION_GIT_TAG")
|
||||
if (env_git_tag != null) {
|
||||
println "Using $env_git_tag from VERSION_GIT_TAG"
|
||||
println "Using $env_git_tag from KEYMAN_VERSION_GIT_TAG"
|
||||
return "$env_git_tag"
|
||||
} else {
|
||||
// Building probably from IDE
|
||||
println "Using release@$VERSION_NAME"
|
||||
return "release@$VERSION_NAME"
|
||||
println "Using release@$KEYMAN_VERSION_NAME"
|
||||
return "release@$KEYMAN_VERSION_NAME"
|
||||
}
|
||||
}
|
||||
ext {
|
||||
VERSION_CODE=getVersionCode()
|
||||
VERSION_MD=getVersionMD()
|
||||
VERSION_NAME=getVersionName()
|
||||
VERSION_ENVIRONMENT=getVersionEnvironment()
|
||||
VERSION_TAG=System.getenv("VERSION_TAG")
|
||||
VERSION_GIT_TAG=getVersionGitTag()
|
||||
KEYMAN_VERSION_CODE=getVersionCode()
|
||||
KEYMAN_VERSION_MD=getVersionMD()
|
||||
KEYMAN_VERSION_NAME=getVersionName()
|
||||
KEYMAN_VERSION_ENVIRONMENT=getVersionEnvironment()
|
||||
KEYMAN_VERSION_TAG=System.getenv("KEYMAN_VERSION_TAG")
|
||||
KEYMAN_VERSION_GIT_TAG=getVersionGitTag()
|
||||
}
|
||||
|
||||
//println "version.gradle: VERSION_TAG: " + VERSION_TAG
|
||||
//println "version.gradle: KEYMAN_VERSION_TAG: " + KEYMAN_VERSION_TAG
|
||||
1
android/zip-excludes
Normal file
1
android/zip-excludes
Normal file
|
|
@ -0,0 +1 @@
|
|||
Samples/*/build.sh
|
||||
|
|
@ -5,6 +5,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
|
|||
. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
|
||||
## END STANDARD BUILD SCRIPT INCLUDE
|
||||
|
||||
. "$KEYMAN_ROOT/resources/build/zip.inc.sh"
|
||||
|
||||
targets=()
|
||||
# Build list of available targets from subfolders, if none specified
|
||||
for d in */; do
|
||||
|
|
@ -29,7 +31,7 @@ builder_parse "$@"
|
|||
function zipsource() {
|
||||
local target="$1"
|
||||
pushd "$1" > /dev/null
|
||||
7z a -r -x!build -x"!$target.kpj.user" "${target}_source.zip" .
|
||||
add_zip_files "${target}_source.zip" -x@../zip-excludes -q -r . # -q quiet, -r recursive
|
||||
popd > /dev/null
|
||||
}
|
||||
|
||||
|
|
@ -40,10 +42,10 @@ function build_index() {
|
|||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Test Keyboards - $VERSION_WITH_TAG</title>
|
||||
<title>Test Keyboards - $KEYMAN_VERSION_WITH_TAG</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Test Keyboards - $VERSION_WITH_TAG</h1>
|
||||
<h1>Test Keyboards - $KEYMAN_VERSION_WITH_TAG</h1>
|
||||
<ul>
|
||||
EOF
|
||||
|
||||
|
|
@ -90,4 +92,4 @@ function build() {
|
|||
fi
|
||||
}
|
||||
|
||||
builder_run_action build build
|
||||
builder_run_action build build
|
||||
|
|
|
|||
3
common/test/keyboards/zip-excludes
Normal file
3
common/test/keyboards/zip-excludes
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
build/
|
||||
*.kpj.user
|
||||
*_source.zip
|
||||
|
|
@ -13,17 +13,9 @@ import vm from 'vm';
|
|||
// Ensure that we can successfully load the module & apply kmwLength, as it's
|
||||
// needed for some of the unit tests.
|
||||
|
||||
// // Verify that the KMW string extensions are loaded via side-effect.
|
||||
|
||||
import { extendString } from '@keymanapp/web-utils';
|
||||
|
||||
import { createRequire } from "module";
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
// Ensure that our KMW string-extensions activate.
|
||||
extendString();
|
||||
assert.ok('💩'.kmwLength);
|
||||
|
||||
/**
|
||||
* Creates a MessageEvent (for inter-worker communication), with the given data payload.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@
|
|||
"homepage": "https://github.com/keymanapp/keyman#readme",
|
||||
"devDependencies": {
|
||||
"@keymanapp/resources-gosh": "*",
|
||||
"mocha": "^10.0.0",
|
||||
"mocha-teamcity-reporter": "^4.0.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
|
|
|
|||
|
|
@ -33,16 +33,16 @@ function do_build() {
|
|||
// Note: does not use the 'default' keyword so that the export name is
|
||||
// correct when converted to a CommonJS module with \`esbuild\`.
|
||||
export class KEYMAN_VERSION {
|
||||
static readonly VERSION = \"$VERSION\";
|
||||
static readonly VERSION_RELEASE =\"$VERSION_RELEASE\";
|
||||
static readonly VERSION_MAJOR = \"$VERSION_MAJOR\";
|
||||
static readonly VERSION_MINOR = \"$VERSION_MINOR\";
|
||||
static readonly VERSION_PATCH = \"$VERSION_PATCH\";
|
||||
static readonly TIER =\"$TIER\";
|
||||
static readonly VERSION_TAG = \"$VERSION_TAG\";
|
||||
static readonly VERSION_WITH_TAG = \"$VERSION_WITH_TAG\";
|
||||
static readonly VERSION_ENVIRONMENT = \"$VERSION_ENVIRONMENT\";
|
||||
static readonly VERSION_GIT_TAG = \"$VERSION_GIT_TAG\";
|
||||
static readonly VERSION = \"$KEYMAN_VERSION\";
|
||||
static readonly VERSION_RELEASE =\"$KEYMAN_VERSION_RELEASE\";
|
||||
static readonly VERSION_MAJOR = \"$KEYMAN_VERSION_MAJOR\";
|
||||
static readonly VERSION_MINOR = \"$KEYMAN_VERSION_MINOR\";
|
||||
static readonly VERSION_PATCH = \"$KEYMAN_VERSION_PATCH\";
|
||||
static readonly TIER =\"$KEYMAN_TIER\";
|
||||
static readonly VERSION_TAG = \"$KEYMAN_VERSION_TAG\";
|
||||
static readonly VERSION_WITH_TAG = \"$KEYMAN_VERSION_WITH_TAG\";
|
||||
static readonly VERSION_ENVIRONMENT = \"$KEYMAN_VERSION_ENVIRONMENT\";
|
||||
static readonly VERSION_GIT_TAG = \"$KEYMAN_VERSION_GIT_TAG\";
|
||||
}
|
||||
|
||||
// Also provides it as a 'default' export.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@
|
|||
"/build/"
|
||||
],
|
||||
"license": "MIT",
|
||||
"scripts": {},
|
||||
"type": "module",
|
||||
"devDependencies": {
|
||||
"typescript": "^5.4.5"
|
||||
|
|
|
|||
|
|
@ -16,8 +16,5 @@ module.exports = {
|
|||
files: "src/**/*.ts",
|
||||
extends: ["../../../common/tools/eslint/eslintNoNodeImports.js"],
|
||||
},
|
||||
],
|
||||
rules: {
|
||||
"prefer-const": "off", // TODO: enable this once infrastructure is in place and cleanup the problem cases
|
||||
},
|
||||
]
|
||||
};
|
||||
|
|
|
|||
|
|
@ -82,7 +82,7 @@ function do_configure() {
|
|||
function do_test() {
|
||||
local MOCHA_FLAGS=
|
||||
|
||||
if [[ "${TEAMCITY_GIT_PATH:-}" != "" ]]; then
|
||||
if builder_is_running_on_teamcity; then
|
||||
# we're running in TeamCity
|
||||
MOCHA_FLAGS="-reporter mocha-teamcity-reporter"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -44,7 +44,6 @@
|
|||
"c8": "^7.12.0",
|
||||
"chalk": "^2.4.2",
|
||||
"hexy": "^0.3.4",
|
||||
"mocha": "^8.4.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"mocha": {
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ export const
|
|||
|
||||
|
||||
export function keymanTargetsFromString(targets: string, options?: {expandTargets?: boolean}): KeymanTarget[] {
|
||||
let result = new Set<KeymanTarget>(<KeymanTarget[]>targets.split(/ +/));
|
||||
const result = new Set<KeymanTarget>(<KeymanTarget[]>targets.split(/ +/));
|
||||
if(options?.expandTargets) {
|
||||
if(result.has(KeymanTarget.any)) {
|
||||
return AllKeymanTargets;
|
||||
|
|
|
|||
|
|
@ -60,14 +60,13 @@ export class KmxFileReader {
|
|||
}
|
||||
|
||||
public read(source: Uint8Array): KEYBOARD {
|
||||
let binaryKeyboard: BUILDER_COMP_KEYBOARD;
|
||||
let kmx = new KMXFile();
|
||||
binaryKeyboard = kmx.COMP_KEYBOARD.fromBuffer(source);
|
||||
const kmx = new KMXFile();
|
||||
const binaryKeyboard = kmx.COMP_KEYBOARD.fromBuffer(source);
|
||||
if(binaryKeyboard.dwIdentifier != KMXFile.FILEID_COMPILED) {
|
||||
throw new KmxFileReaderError(`Not a .kmx file: header does not contain FILEID_COMPILED`);
|
||||
}
|
||||
|
||||
let result = new KEYBOARD();
|
||||
const result = new KEYBOARD();
|
||||
result.fileVersion = binaryKeyboard.dwFileVersion;
|
||||
result.flags = binaryKeyboard.dwFlags;
|
||||
result.hotkey = binaryKeyboard.dwHotKey;
|
||||
|
|
@ -88,7 +87,7 @@ export class KmxFileReader {
|
|||
this.readGroupsAndRules(binaryKeyboard, kmx, source, result);
|
||||
|
||||
// Process system stores once we have all stores and groups loaded
|
||||
for(let store of result.stores) {
|
||||
for(const store of result.stores) {
|
||||
this.processSystemStore(store, result);
|
||||
}
|
||||
|
||||
|
|
@ -109,8 +108,8 @@ export class KmxFileReader {
|
|||
let offset = binaryKeyboard.dpGroupArray;
|
||||
for (let i = 0; i < binaryKeyboard.cxGroupArray; i++) {
|
||||
const data = new Uint8Array(source.buffer, source.byteOffset + offset);
|
||||
let binaryGroup = kmx.COMP_GROUP.fromBuffer(data);
|
||||
let group = new GROUP();
|
||||
const binaryGroup = kmx.COMP_GROUP.fromBuffer(data);
|
||||
const group = new GROUP();
|
||||
group.dpMatch = this.readString(source, binaryGroup.dpMatch);
|
||||
group.dpName = this.readString(source, binaryGroup.dpName);
|
||||
group.dpNoMatch = this.readString(source, binaryGroup.dpNoMatch);
|
||||
|
|
@ -120,8 +119,8 @@ export class KmxFileReader {
|
|||
let keyOffset = binaryGroup.dpKeyArray;
|
||||
for (let j = 0; j < binaryGroup.cxKeyArray; j++) {
|
||||
const keyData = new Uint8Array(source.buffer, source.byteOffset + keyOffset);
|
||||
let binaryKey = kmx.COMP_KEY.fromBuffer(keyData);
|
||||
let key = new KEY();
|
||||
const binaryKey = kmx.COMP_KEY.fromBuffer(keyData);
|
||||
const key = new KEY();
|
||||
key.Key = binaryKey.Key;
|
||||
key.Line = binaryKey.Line;
|
||||
key.ShiftFlags = binaryKey.ShiftFlags;
|
||||
|
|
@ -140,8 +139,8 @@ export class KmxFileReader {
|
|||
let offset = binaryKeyboard.dpStoreArray;
|
||||
for (let i = 0; i < binaryKeyboard.cxStoreArray; i++) {
|
||||
const data = new Uint8Array(source.buffer, source.byteOffset + offset);
|
||||
let binaryStore = kmx.COMP_STORE.fromBuffer(data);
|
||||
let store = new STORE();
|
||||
const binaryStore = kmx.COMP_STORE.fromBuffer(data);
|
||||
const store = new STORE();
|
||||
store.dwSystemID = binaryStore.dwSystemID;
|
||||
store.dpName = this.readString(source, binaryStore.dpName);
|
||||
store.dpString = this.readString(source, binaryStore.dpString);
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -69,7 +69,7 @@ export class ElementString extends Array<ElemElement> {
|
|||
}
|
||||
|
||||
for(let i = 0; i < items.length; i++) {
|
||||
let elem = new ElemElement();
|
||||
const elem = new ElemElement();
|
||||
const item = items[i];
|
||||
let typeFlag = 0;
|
||||
if (item.type === ElementType.uset) {
|
||||
|
|
@ -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 {
|
||||
let 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;
|
||||
|
|
@ -546,7 +564,7 @@ export class Keys extends Section {
|
|||
kmap: KeysKmap[] = [];
|
||||
constructor(strs: Strs) {
|
||||
super();
|
||||
let nullFlicks = new KeysFlicks(strs.allocString(''));
|
||||
const nullFlicks = new KeysFlicks(strs.allocString(''));
|
||||
this.flicks.push(nullFlicks); // C7043: null element string
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import KVKFile, { BUILDER_KVK_FILE, BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_HEADER_VERSION } from "./kvk-file.js";
|
||||
import KVKFile, { BUILDER_KVK_HEADER_IDENTIFIER, BUILDER_KVK_HEADER_VERSION } from "./kvk-file.js";
|
||||
import { VisualKeyboard, VisualKeyboardKey } from "./visual-keyboard.js";
|
||||
|
||||
export default class KvkFileReader {
|
||||
public read(source: Uint8Array): VisualKeyboard {
|
||||
let binary: BUILDER_KVK_FILE;
|
||||
let kvk = new KVKFile();
|
||||
binary = kvk.KVK_FILE.fromBuffer(source);
|
||||
const kvk = new KVKFile();
|
||||
const binary = kvk.KVK_FILE.fromBuffer(source);
|
||||
if(binary.header.identifier != BUILDER_KVK_HEADER_IDENTIFIER ||
|
||||
binary.header.version != BUILDER_KVK_HEADER_VERSION) {
|
||||
return null;
|
||||
}
|
||||
let result = new VisualKeyboard();
|
||||
const result = new VisualKeyboard();
|
||||
result.header.version = binary.header.version;
|
||||
result.header.flags = binary.header.flags;
|
||||
result.header.associatedKeyboard = binary.header.associatedKeyboard.str;
|
||||
|
|
@ -18,8 +17,8 @@ export default class KvkFileReader {
|
|||
result.header.ansiFont.size = binary.header.ansiFont.size;
|
||||
result.header.unicodeFont.name = binary.header.unicodeFont.name.str;
|
||||
result.header.unicodeFont.size = binary.header.unicodeFont.size;
|
||||
for(let binaryKey of binary.keys) {
|
||||
let key: VisualKeyboardKey = {
|
||||
for(const binaryKey of binary.keys) {
|
||||
const key: VisualKeyboardKey = {
|
||||
flags: binaryKey.flags,
|
||||
vkey: binaryKey.vkey,
|
||||
shift: binaryKey.shift,
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ export default class KvkFileWriter {
|
|||
this.setString(binary.header.ansiFont.name, source.header.ansiFont.name);
|
||||
this.setString(binary.header.unicodeFont.name, source.header.unicodeFont.name);
|
||||
|
||||
for(let sourceKey of source.keys) {
|
||||
for(const sourceKey of source.keys) {
|
||||
const binaryKey: BUILDER_KVK_KEY = {
|
||||
flags: sourceKey.flags,
|
||||
vkey: sourceKey.vkey,
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ export class MarkerParser {
|
|||
}
|
||||
}
|
||||
// Then, take each codepoint (from back to front)
|
||||
for (let p of [...s].reverse()) {
|
||||
for (const p of [...s].reverse()) {
|
||||
// reverse order code units, prepend to out
|
||||
out = p + out;
|
||||
|
||||
|
|
@ -525,7 +525,7 @@ export class ElementParser {
|
|||
throw Error(`Unsupported: nested square brackets in element segment: ${str}`);
|
||||
}
|
||||
const list: ElementSegment[] = [];
|
||||
for(let m of str.match(ElementParser.MATCH_ELEMENT_SEGMENTS)) {
|
||||
for(const m of str.match(ElementParser.MATCH_ELEMENT_SEGMENTS)) {
|
||||
const e = new ElementSegment(m);
|
||||
if (e.type === ElementType.escaped) {
|
||||
// unescape
|
||||
|
|
@ -534,7 +534,7 @@ export class ElementParser {
|
|||
list.push(e);
|
||||
} else {
|
||||
// need to split the escaped segment, \u{41 42} -> \u{41}, \u{42}
|
||||
for (let s of unescaped) {
|
||||
for (const s of unescaped) {
|
||||
list.push(new ElementSegment(`\\u{${s.codePointAt(0).toString(16)}}`));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ export class ListItem extends Array<ListIndex> implements OrderedStringList {
|
|||
return a;
|
||||
}
|
||||
for (const str of source) {
|
||||
let index = new ListIndex(sections.strs.allocString(str, opts, sections));
|
||||
const index = new ListIndex(sections.strs.allocString(str, opts, sections));
|
||||
a.push(index);
|
||||
}
|
||||
return a;
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -5,18 +5,6 @@
|
|||
|
||||
/****************************** Lexical Models ******************************/
|
||||
|
||||
/**
|
||||
* A JavaScript string with the restriction that it must only
|
||||
* contain Unicode scalar values.
|
||||
*
|
||||
* This means that any lone high surrogate must be paired with
|
||||
* a low surrogate, if it exists. Lone surrogate code units are
|
||||
* forbidden.
|
||||
*
|
||||
* See also: https://developer.mozilla.org/en-US/docs/Web/API/USVString
|
||||
*/
|
||||
export type USVString = string;
|
||||
|
||||
export type CasingForm = 'lower' | 'initial' | 'upper';
|
||||
|
||||
/**
|
||||
|
|
@ -67,7 +55,7 @@ export interface LexiconTraversal {
|
|||
* - If `char` = 'e', the child represents a prefix of 'the'.
|
||||
* - Then `traversal` allows traversing the part of the lexicon prefixed by 'the'.
|
||||
*/
|
||||
children(): Generator<{char: USVString, traversal: () => LexiconTraversal}>;
|
||||
children(): Generator<{char: string, traversal: () => LexiconTraversal}>;
|
||||
|
||||
/**
|
||||
* Allows direct access to the traversal state that results when appending one
|
||||
|
|
@ -81,7 +69,7 @@ export interface LexiconTraversal {
|
|||
* That is, if a model "keys" `è` to `e`, there will be no `è` child.
|
||||
* @param char
|
||||
*/
|
||||
child(char: USVString): LexiconTraversal | undefined;
|
||||
child(char: string): LexiconTraversal | undefined;
|
||||
|
||||
/**
|
||||
* Any entries directly keyed by the currently-represented lookup prefix. Entries and
|
||||
|
|
@ -178,7 +166,7 @@ export interface LexicalModel {
|
|||
* @param text The original input text.
|
||||
* @returns The 'keyed' form of that text.
|
||||
*/
|
||||
toKey?(text: USVString): USVString;
|
||||
toKey?(text: string): string;
|
||||
|
||||
/**
|
||||
* Generates predictive suggestions corresponding to the state of context after the proposed
|
||||
|
|
@ -248,7 +236,7 @@ export interface LexicalModel {
|
|||
* @param context
|
||||
* @deprecated
|
||||
*/
|
||||
wordbreak?(context: Context): USVString;
|
||||
wordbreak?(context: Context): string;
|
||||
|
||||
/**
|
||||
* Lexical models _may_ provide a LexiconTraversal object usable to enhance
|
||||
|
|
@ -279,7 +267,7 @@ export interface Transform {
|
|||
*
|
||||
* Corresponds to `s` in com.keyman.KeyboardInterface.output.
|
||||
*/
|
||||
insert: USVString;
|
||||
insert: string;
|
||||
|
||||
/**
|
||||
* The number of code units to delete to the left of the cursor.
|
||||
|
|
@ -380,7 +368,7 @@ export interface Context {
|
|||
* buffer. If there is nothing to the left of the buffer, this is
|
||||
* an empty string.
|
||||
*/
|
||||
readonly left: USVString;
|
||||
readonly left: string;
|
||||
|
||||
/**
|
||||
* Up to maxRightContextCodeUnits code units of Unicode scalar value
|
||||
|
|
@ -390,7 +378,7 @@ export interface Context {
|
|||
*
|
||||
* This property may be missing entirely.
|
||||
*/
|
||||
readonly right?: USVString;
|
||||
readonly right?: string;
|
||||
|
||||
/**
|
||||
* Whether the insertion point is at the start of the buffer.
|
||||
|
|
|
|||
|
|
@ -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("𐌰𐌱𐌲"),
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
|
||||
import { KMXPlus, LdmlKeyboardTypes, LexicalModelTypes } from "@keymanapp/common-types";
|
||||
|
||||
export let u: LexicalModelTypes.USVString;
|
||||
export let u: string;
|
||||
export let l: LexicalModelTypes.Transform;
|
||||
export let s: LexicalModelTypes.Suggestion;
|
||||
export let st: LexicalModelTypes.SuggestionTag;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ var
|
|||
KeymanRoot: string;
|
||||
ReportPath: string;
|
||||
begin
|
||||
if GetEnvironmentVariable('TEAMCITY_VERSION') <> '' then
|
||||
if GetEnvironmentVariable('TEAMCITY_GIT_PATH') <> '' then
|
||||
begin
|
||||
KeymanRoot := ExcludeTrailingPathDelimiter(GetEnvironmentVariable('KEYMAN_ROOT'));
|
||||
ReportPath := ExtractRelativePath(KeymanRoot, ExtractFilePath(ParamStr(0)) + 'dunitx-results.xml');
|
||||
|
|
|
|||
|
|
@ -69,7 +69,8 @@ Libraries will be built in 'build/<target>/<configuration>/src'.
|
|||
"uninstall uninstall libraries from current system" \
|
||||
"${archtargets[@]}" \
|
||||
"--no-tests do not configure tests (used by other projects)" \
|
||||
"--test,-t=opt_tests test[s] to run (space separated)"
|
||||
"--test,-t=opt_tests test[s] to run (space separated)" \
|
||||
"--no-werror don't report warnings as errors"
|
||||
|
||||
builder_parse "$@"
|
||||
|
||||
|
|
@ -115,12 +116,15 @@ builder_describe_outputs \
|
|||
build:arch /core/build/arch/$BUILDER_CONFIGURATION/src/libkeymancore.a \
|
||||
build:wasm /core/build/wasm/$BUILDER_CONFIGURATION/src/libkeymancore.a
|
||||
|
||||
# Import our standard compiler defines; this is copied from
|
||||
# /resources/build/meson/standard.meson.build by build.sh, because meson doesn't
|
||||
# allow us to reference a file outside its root
|
||||
MESON_ARGS=--werror
|
||||
if builder_has_option --no-werror; then
|
||||
MESON_ARGS=
|
||||
fi
|
||||
|
||||
if builder_has_action configure; then
|
||||
mkdir -p "$THIS_SCRIPT_PATH/resources"
|
||||
cp "$KEYMAN_ROOT/resources/build/meson/standard.meson.build" "$THIS_SCRIPT_PATH/resources/meson.build"
|
||||
# Import our standard compiler defines
|
||||
source "$KEYMAN_ROOT/resources/build/meson/standard_meson_build.inc.sh"
|
||||
standard_meson_build
|
||||
fi
|
||||
|
||||
# Iterate through all possible targets; note that targets that cannot be built
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ do_configure() {
|
|||
else
|
||||
pushd "$THIS_SCRIPT_PATH" > /dev/null
|
||||
# Additional arguments are used by Linux build, e.g. -Dprefix=${INSTALLDIR}
|
||||
meson setup "$MESON_PATH" $MESON_CROSS_FILE --werror --buildtype $BUILDER_CONFIGURATION $STANDARD_MESON_ARGS "${builder_extra_params[@]}"
|
||||
meson setup "$MESON_PATH" $MESON_CROSS_FILE ${MESON_ARGS} --buildtype $BUILDER_CONFIGURATION $STANDARD_MESON_ARGS "${builder_extra_params[@]}"
|
||||
popd > /dev/null
|
||||
fi
|
||||
|
||||
|
|
|
|||
|
|
@ -1,3 +0,0 @@
|
|||
@echo off
|
||||
REM Get the version number from VERSION.md
|
||||
more ..\VERSION.md
|
||||
|
|
@ -1,3 +0,0 @@
|
|||
#!/usr/bin/env bash
|
||||
# Get the version number from VERSION.md
|
||||
cat ../VERSION.md
|
||||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@
|
|||
#
|
||||
|
||||
project('keyman_core', 'cpp', 'c',
|
||||
version: run_command(find_program('getversion.bat', 'getversion.sh'), check:true).stdout().strip(),
|
||||
version: files('../VERSION.md'),
|
||||
license: 'MIT',
|
||||
default_options : ['buildtype=release',
|
||||
'cpp_std=c++17',
|
||||
|
|
|
|||
13
core/tests/unit/kmx/keyboards.kpj.in
Normal file
13
core/tests/unit/kmx/keyboards.kpj.in
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<KeymanDeveloperProject>
|
||||
<Options>
|
||||
<BuildPath>$PROJECTPATH</BuildPath>
|
||||
<CompilerWarningsAsErrors>False</CompilerWarningsAsErrors>
|
||||
<WarnDeprecatedCode>False</WarnDeprecatedCode>
|
||||
<CheckFilenameConventions>False</CheckFilenameConventions>
|
||||
<ProjectType>keyboard</ProjectType>
|
||||
</Options>
|
||||
<Files>
|
||||
@keyboards_kpj@
|
||||
</Files>
|
||||
</KeymanDeveloperProject>
|
||||
|
|
@ -99,13 +99,14 @@ else
|
|||
cat_cmd = [find_program('cat', required: true)]
|
||||
endif
|
||||
|
||||
test_keyboards = ''
|
||||
test_keyboards_kpj = ''
|
||||
test_keyboards_kmp_json = ''
|
||||
foreach kbd : tests
|
||||
if test_keyboards.startswith('{')
|
||||
test_keyboards += ''',
|
||||
if test_keyboards_kmp_json.startswith('{')
|
||||
test_keyboards_kmp_json += ''',
|
||||
'''
|
||||
endif
|
||||
test_keyboards += '''{
|
||||
test_keyboards_kmp_json += '''{
|
||||
"name": "''' + kbd + '''",
|
||||
"id": "''' + kbd + '''",
|
||||
"version": "0.0",
|
||||
|
|
@ -116,16 +117,46 @@ foreach kbd : tests
|
|||
}
|
||||
]
|
||||
}'''
|
||||
|
||||
test_keyboards_kpj += '''
|
||||
<File>
|
||||
<ID>keyboard_'''+kbd+'''</ID>
|
||||
<Filename>'''+kbd+'''.kmn</Filename>
|
||||
<Filepath>'''+kbd+'''.kmn</Filepath>
|
||||
</File>
|
||||
<File>
|
||||
<ID>package_'''+kbd+'''</ID>
|
||||
<Filename>'''+kbd+'''.kps</Filename>
|
||||
<Filepath>'''+kbd+'''.kps</Filepath>
|
||||
</File>
|
||||
'''
|
||||
endforeach
|
||||
|
||||
cfg = configuration_data()
|
||||
cfg.set('keyboards', test_keyboards)
|
||||
cfg.set('keyboards', test_keyboards_kmp_json)
|
||||
cfg.set('keyboards_kpj', test_keyboards_kpj)
|
||||
|
||||
# Build a single kmp.json which lists all compiled keyboards
|
||||
configure_file(
|
||||
configuration: cfg,
|
||||
input: 'kmp.json.in',
|
||||
output: 'kmp.json'
|
||||
)
|
||||
|
||||
# Build a single keyboards.kpj which lists all keyboard + package sources
|
||||
keyboards_kpj = configure_file(
|
||||
configuration: cfg,
|
||||
input: 'keyboards.kpj.in',
|
||||
output: 'keyboards.kpj'
|
||||
)
|
||||
|
||||
all_keyboards = custom_target('all_keyboards',
|
||||
input: keyboards_kpj,
|
||||
output: [tests[0]+'.kmp'],
|
||||
depfile: 'keyboards.dep',
|
||||
command: kmc_cmd + ['build', '--debug', '--no-compiler-version', '@INPUT@']
|
||||
)
|
||||
|
||||
foreach kbd : tests
|
||||
kbd_src_path = common_test_keyboards_baseline / kbd + '.kmn'
|
||||
|
||||
|
|
@ -151,24 +182,15 @@ foreach kbd : tests
|
|||
input: kbd_src_path,
|
||||
output: kbd + '.kmn'
|
||||
)
|
||||
kbd_kpj = configure_file(
|
||||
configuration: cfg,
|
||||
input: join_paths('template', 'keyboard.kpj.in'),
|
||||
output: kbd + '.kpj'
|
||||
)
|
||||
kbd_kps = configure_file(
|
||||
configuration: cfg,
|
||||
input: join_paths('template', 'keyboard.kps.in'),
|
||||
output: kbd + '.kps'
|
||||
)
|
||||
kbd_kmp = custom_target(kbd + '.kmp',
|
||||
input: kbd_kpj,
|
||||
output: [kbd + '.kmp', kbd + '.kmx'],
|
||||
depend_files: [kbd_kps, kbd_kmn],
|
||||
depfile: kbd + '.dep',
|
||||
command: kmc_cmd + ['build', '--debug', '--no-compiler-version', '@INPUT@'])
|
||||
endforeach
|
||||
|
||||
test(kbd, kmx, depends: [kbd_kmp], args: [test_path / kbd + '.kmn', test_path / kbd + '.kmx'])
|
||||
foreach kbd : tests
|
||||
test(kbd, kmx, depends: [all_keyboards], args: [test_path / kbd + '.kmn', test_path / kbd + '.kmx'])
|
||||
endforeach
|
||||
|
||||
# binary file unit tests
|
||||
|
|
|
|||
13
core/tests/unit/ldml/keyboards/keyboards.kpj.in
Normal file
13
core/tests/unit/ldml/keyboards/keyboards.kpj.in
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<KeymanDeveloperProject>
|
||||
<Options>
|
||||
<BuildPath>$PROJECTPATH</BuildPath>
|
||||
<CompilerWarningsAsErrors>False</CompilerWarningsAsErrors>
|
||||
<WarnDeprecatedCode>False</WarnDeprecatedCode>
|
||||
<CheckFilenameConventions>False</CheckFilenameConventions>
|
||||
<ProjectType>keyboard</ProjectType>
|
||||
</Options>
|
||||
<Files>
|
||||
@keyboards_kpj@
|
||||
</Files>
|
||||
</KeymanDeveloperProject>
|
||||
|
|
@ -65,13 +65,7 @@ kmc_cmd = [node, '--enable-source-maps', kmc_root]
|
|||
# Build all keyboards in output folder
|
||||
|
||||
foreach kbd : tests_needing_copy
|
||||
fs.copyfile(kbd + '.xml', kbd + '.xml')
|
||||
|
||||
configure_file(
|
||||
command: kmc_cmd + ['build', '@INPUT@', '--out-file', '@OUTPUT@'],
|
||||
input: kbd + '.xml',
|
||||
output: kbd + '.kmx',
|
||||
)
|
||||
configure_file(input: kbd + '.xml', output: kbd + '.xml', copy: true)
|
||||
endforeach
|
||||
|
||||
foreach kbd : tests_with_testdata
|
||||
|
|
@ -83,15 +77,40 @@ foreach kbd : tests_with_testdata
|
|||
endforeach
|
||||
|
||||
foreach kbd : tests_from_cldr
|
||||
fs.copyfile(ldml_data / kbd + '.xml', kbd + '.xml')
|
||||
configure_file(
|
||||
command: kmc_cmd + ['build', '@INPUT@', '--out-file', '@OUTPUT@'],
|
||||
input: join_paths(ldml_data, kbd + '.xml'),
|
||||
output: kbd + '.kmx',
|
||||
)
|
||||
configure_file(input: ldml_data / kbd + '.xml', output: kbd + '.xml', copy: true)
|
||||
configure_file(
|
||||
command: kmc_cmd + ['build', 'ldml-test-data', '@INPUT@', '--out-file', '@OUTPUT@'],
|
||||
input: join_paths(ldml_testdata, kbd + '-test.xml'),
|
||||
output: kbd + '-test.json',
|
||||
)
|
||||
endforeach
|
||||
|
||||
# Build a single keyboards.kpj which lists all keyboard sources
|
||||
|
||||
test_ldml_kmx_targets = tests_needing_copy + tests_from_cldr
|
||||
|
||||
test_ldml_keyboards_kpj = ''
|
||||
foreach kbd : test_ldml_kmx_targets
|
||||
test_ldml_keyboards_kpj += '''
|
||||
<File>
|
||||
<ID>keyboard_'''+kbd+'''</ID>
|
||||
<Filename>'''+kbd+'''.xml</Filename>
|
||||
<Filepath>'''+kbd+'''.xml</Filepath>
|
||||
</File>
|
||||
'''
|
||||
endforeach
|
||||
|
||||
cfg = configuration_data()
|
||||
cfg.set('keyboards_kpj', test_ldml_keyboards_kpj)
|
||||
|
||||
ldml_keyboards_kpj = configure_file(
|
||||
configuration: cfg,
|
||||
input: 'keyboards.kpj.in',
|
||||
output: 'keyboards.kpj'
|
||||
)
|
||||
|
||||
configure_file(
|
||||
input: ldml_keyboards_kpj,
|
||||
output: tests_from_cldr[0]+'.kmx',
|
||||
command: kmc_cmd + ['build', '@INPUT@']
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,5 +2,6 @@
|
|||
"name": "@keymanapp/api-header-extractor",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {},
|
||||
"main": "build/src/index.js"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -240,6 +240,10 @@ controlled from the **Device** menu.
|
|||
This will be most useful when running Server on macOS or Linux, where
|
||||
integration with the Keyman Developer IDE is not available.
|
||||
|
||||
Note that for security reasons, upload is only available when accessing
|
||||
Keyman Developer Server on a localhost URL (127.0.0.1, ::1, or
|
||||
::ffff:127.0.0.1).
|
||||
|
||||
You can also drag and drop these same files onto the page.
|
||||
|
||||
**Help...**
|
||||
|
|
|
|||
140
developer/docs/internal/developer-messaging.md
Normal file
140
developer/docs/internal/developer-messaging.md
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
---
|
||||
author: "Steven R. Loomis <srl295@gmail.com>"
|
||||
date: "2025-05-19"
|
||||
issue: "https://github.com/keymanapp/keyman/issues/14020"
|
||||
---
|
||||
|
||||
# Keyman Developer Messaging
|
||||
|
||||
## Summary
|
||||
|
||||
This document discusses the mechanism for kmc Compiler \+ Toolchain Messages in Keyman Developer.
|
||||
|
||||
## Objectives
|
||||
|
||||
1. Provide developers (end users) with feedback on the failure (or success) of their use of Keyman Developer tools. (Compilers and others: All compilers are tools, but not all tools are compilers.)
|
||||
2. Enable that feedback to be actionable:
|
||||
1. Clear: so the developer knows **what** happened.
|
||||
2. Concise: so the developer can identify the relevant parts of the message without extraneous information. Enable deduplication: allow the consumer of the messages to deduplicate messages, but don't deduplicate them prematurely.
|
||||
3. Context: so the developer knows **where** the issue happened. Exactly what part of the process, which file, which line/column number.
|
||||
Enable presentation of failing LOC by message consumer
|
||||
3. Empower the \#developer-team of Keyman to effectively author these messages, that is, encourage deployment of messages meeting above goals
|
||||
1. Contain ceremony (repetitive content) around message call sites
|
||||
1. declutter the calling code
|
||||
2. encourage developers to make use of distinct messaging vs. 'catch-alls': "Something Bad Happened \[Here\]"
|
||||
2. Contain burden on tool/platform implementation code
|
||||
3. Single mechanism for reporting (and it's not `console.log`).
|
||||
1. we don't expect exceptions to bubble up to users, that's an exceptional \[sic\] case.
|
||||
4. [publish messages to the website](https://help.keyman.com/developer/current-version/reference/messages/)
|
||||
5. validation/ linting
|
||||
6. formal representation of parameterized messages rather than string concatenation or in-compiler message massaging code
|
||||
1. in short, string content should live in the messages, not the compiler
|
||||
4. Responsibility of consumer (i.e. CLI tool, IDE) to:
|
||||
1. format
|
||||
2. display
|
||||
3. future: localize ( i18n )?
|
||||
|
||||
## Details of the Current Mechanism
|
||||
|
||||
### Types
|
||||
|
||||
#### `CompilerEvent`
|
||||
|
||||
* `filename` — string
|
||||
* `offset` — number, in some cases, resolves to line:column
|
||||
* `line` — number, may be set directoly
|
||||
* `column` — number, may be set directly
|
||||
* `code` — hex, [see below](#code)
|
||||
* `message` — resolved formatted text
|
||||
* `detail` — Markdown with more detail and suggestions
|
||||
* `exceptionVar` — used for internal tracking of exceptions
|
||||
|
||||
### Code
|
||||
|
||||
1. [https://help.keyman.com/developer/current-version/reference/messages/](https://help.keyman.com/developer/current-version/reference/messages/)
|
||||
2. Example: `KM03009: ERROR_UnknownWordBreaker`
|
||||
|
||||
* `00503009` - hex value
|
||||
* `RRSNNBBB` \- (see below)
|
||||
* `RR______` \- Reserved
|
||||
* `__S_____` \- Severity, example, `5` = error
|
||||
* `___NN___` \- Namespace, example `03` = model compiler
|
||||
* `_____BBB` \- Base error, ex `009`= "unknown word breaker"
|
||||
|
||||
### Callback
|
||||
|
||||
The `callbacks` object includes the function:
|
||||
|
||||
* `reportMessage(e: CompilerEvent)` provided by the consumer
|
||||
|
||||
We have chained/wrapper callbacks in some cases.
|
||||
|
||||
### Metadata
|
||||
|
||||
* The fast-xml-parser package has an option to generate metadata (via a special symbol property) on each object in the parse tree.
|
||||
* This metadata includes line numbering.
|
||||
|
||||
```js
|
||||
interface XMLMetaData { startIndex?: number };
|
||||
```
|
||||
|
||||
* We extend this metadata concept to include symbols for import status and filename, in the case of imported files.
|
||||
|
||||
## Flow
|
||||
|
||||
### Call Site
|
||||
|
||||
Example: keys.ts \- compiler \- call site for error
|
||||
|
||||
```js
|
||||
this.callbacks.reportMessage(
|
||||
LdmlCompilerMessages.Error_InvalidScanCode({
|
||||
invalidCodeList: codes.join(' '), id:x?.id }, form)
|
||||
);
|
||||
```
|
||||
|
||||
* `{ id, codes }` \- message parameters (optional)
|
||||
* `form` \- object that caused the error (contains line number metadata)
|
||||
|
||||
### Message Expansion
|
||||
|
||||
Message expansion `m()` or `mx()` (in LDML) function is used.
|
||||
|
||||
```js
|
||||
static ERROR_InvalidScanCode = SevError | 0x0009;
|
||||
static Error_InvalidScanCode = (o:{id: string, invalidCodeList: string}, x: ObjectWithMetadata) => mx(
|
||||
this.ERROR_InvalidScanCode, context,
|
||||
Form '${def(o.id)}' has invalid/unknown scancodes '${def(o.codes)}',
|
||||
`…additional markdown detail…`
|
||||
);
|
||||
```
|
||||
|
||||
Parameters:
|
||||
|
||||
* 'o' (Object) used for parameters- here, the object id and the codes.
|
||||
* 'x' (conteXt) used for extracting the offset, which will be resolved to line numbers.
|
||||
|
||||
_Q: But wait, doesn't `x.id` already have the 'id'? DRY!?_
|
||||
|
||||
_A: Yes, however, we want a clean separation between parameters and context. This way, even though repetitive, makes it clear exactly what's being passed and consumed as a parameter. `x` has other irrelevent stuff on it._
|
||||
|
||||
That is, no additional code in the \*_*() function (the caller of m(), mx() \- put helper functions outside ,move into the call site)
|
||||
|
||||
### Context extraction
|
||||
|
||||
The `mx()` function does the following (note that `m()` is called first).
|
||||
|
||||
```js
|
||||
let evt = m(code, message, detail); // raw message
|
||||
evt = LdmlCompilerMessages.offset(evt, x); // with offset
|
||||
```
|
||||
|
||||
Note: We could have: `ml(e:CompilerEvent, lineNumber?: number)` so that compilers with a line number could pass that in directly.
|
||||
|
||||
### Callbacks
|
||||
|
||||
|
||||
`reportMessage()`
|
||||
|
||||
* pre-process if needed \- expand `offset` (ex: byte offset 276\) to `line:column` (line 13 column 4\)
|
||||
* "end user" of compiler receives result with `messages[]` array
|
||||
|
|
@ -11,7 +11,7 @@
|
|||
"dependencies": {
|
||||
"@keymanapp/common-types": "*",
|
||||
"eventemitter3": "^5.0.0",
|
||||
"fast-xml-parser": "^5.0.9",
|
||||
"fast-xml-parser": "^5.2.2",
|
||||
"path-browserify": "^1.0.1",
|
||||
"restructure": "^3.0.1",
|
||||
"sax": ">=0.6.0",
|
||||
|
|
@ -25,7 +25,6 @@
|
|||
"@types/semver": "^7.3.12",
|
||||
"c8": "^7.12.0",
|
||||
"git-diff": "^2.0.6",
|
||||
"mocha": "^8.4.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,21 @@
|
|||
|
||||
import { CompilerEvent, CompilerCallbackOptions, CompilerErrorSeverity, CompilerError, CompilerMessageOverrideMap, CompilerErrorMask } from "./compiler-interfaces.js";
|
||||
|
||||
/**
|
||||
* The EventResolver implementation is able to expand error messages,
|
||||
* particularly to change a byte offset into a line number.
|
||||
*/
|
||||
export interface EventResolver {
|
||||
resolve(event: CompilerEvent): void;
|
||||
}
|
||||
|
||||
/** A do-nothing EventResolver. Implementations can use this as their default EventResolver. */
|
||||
export class NullEventResolver implements EventResolver {
|
||||
resolve(event: CompilerEvent): void {
|
||||
// do nothing, this is the null event resolver.
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Abstract interface for callbacks, to abstract out file i/o
|
||||
*/
|
||||
|
|
@ -40,6 +55,10 @@ export interface CompilerCallbacks {
|
|||
*/
|
||||
resolveFilename(baseFilename: string, filename: string): string;
|
||||
|
||||
/**
|
||||
* Report a message from the compiler back to the caller.
|
||||
* @param event
|
||||
*/
|
||||
reportMessage(event: CompilerEvent): void;
|
||||
|
||||
debug(msg: string): void;
|
||||
|
|
@ -106,14 +125,64 @@ export interface CompilerFileSystemAsyncCallbacks {
|
|||
resolveFilename(baseFilename: string, filename: string): string;
|
||||
}
|
||||
|
||||
/** a CompilerCallbacks implementation that delegates to a parent */
|
||||
export class DelegatingCompilerCallbacks implements CompilerCallbacks {
|
||||
constructor(protected options: CompilerCallbackOptions, protected parent: CompilerCallbacks) {
|
||||
}
|
||||
|
||||
loadFile(filename: string): Uint8Array {
|
||||
return this.parent.loadFile(filename);
|
||||
}
|
||||
|
||||
fileSize(filename: string): number {
|
||||
return this.parent.fileSize(filename);
|
||||
}
|
||||
|
||||
isDirectory(filename: string): boolean {
|
||||
return this.parent.isDirectory(filename);
|
||||
}
|
||||
|
||||
get path(): CompilerPathCallbacks {
|
||||
return this.parent.path;
|
||||
}
|
||||
|
||||
get fs(): CompilerFileSystemCallbacks {
|
||||
return this.parent.fs;
|
||||
}
|
||||
|
||||
get net(): CompilerNetAsyncCallbacks {
|
||||
return this.parent.net;
|
||||
}
|
||||
|
||||
get fsAsync(): CompilerFileSystemAsyncCallbacks {
|
||||
return this.parent.fsAsync;
|
||||
}
|
||||
|
||||
resolveFilename(baseFilename: string, filename: string): string {
|
||||
return this.parent.resolveFilename(baseFilename, filename);
|
||||
}
|
||||
|
||||
debug(msg: string): void {
|
||||
return this.parent.debug(msg);
|
||||
}
|
||||
|
||||
fileURLToPath(url: string | URL): string {
|
||||
return this.parent.fileURLToPath(url);
|
||||
}
|
||||
|
||||
reportMessage(event: CompilerEvent) {
|
||||
this.parent.reportMessage(event);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrapper class for CompilerCallbacks for a given input file
|
||||
*/
|
||||
export class CompilerFileCallbacks implements CompilerCallbacks {
|
||||
export class CompilerFileCallbacks extends DelegatingCompilerCallbacks {
|
||||
messages: CompilerEvent[] = [];
|
||||
|
||||
constructor(private filename: string, private options: CompilerCallbackOptions, private parent: CompilerCallbacks) {
|
||||
constructor(private filename: string, options: CompilerCallbackOptions, parent: CompilerCallbacks) {
|
||||
super(options, parent);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -170,38 +239,6 @@ export class CompilerFileCallbacks implements CompilerCallbacks {
|
|||
this.messages = [];
|
||||
}
|
||||
|
||||
loadFile(filename: string): Uint8Array {
|
||||
return this.parent.loadFile(filename);
|
||||
}
|
||||
|
||||
fileSize(filename: string): number {
|
||||
return this.parent.fileSize(filename);
|
||||
}
|
||||
|
||||
isDirectory(filename: string): boolean {
|
||||
return this.parent.isDirectory(filename);
|
||||
}
|
||||
|
||||
get path(): CompilerPathCallbacks {
|
||||
return this.parent.path;
|
||||
}
|
||||
|
||||
get fs(): CompilerFileSystemCallbacks {
|
||||
return this.parent.fs;
|
||||
}
|
||||
|
||||
get net(): CompilerNetAsyncCallbacks {
|
||||
return this.parent.net;
|
||||
}
|
||||
|
||||
get fsAsync(): CompilerFileSystemAsyncCallbacks {
|
||||
return this.parent.fsAsync;
|
||||
}
|
||||
|
||||
resolveFilename(baseFilename: string, filename: string): string {
|
||||
return this.parent.resolveFilename(baseFilename, filename);
|
||||
}
|
||||
|
||||
reportMessage(event: CompilerEvent): void {
|
||||
const disable = CompilerFileCallbacks.applyMessageOverridesToEvent(event, this.options.messageOverrides);
|
||||
this.messages.push(event);
|
||||
|
|
@ -209,14 +246,6 @@ export class CompilerFileCallbacks implements CompilerCallbacks {
|
|||
this.parent.reportMessage({ filename: this.filename, ...event });
|
||||
}
|
||||
}
|
||||
|
||||
debug(msg: string): void {
|
||||
return this.parent.debug(msg);
|
||||
}
|
||||
|
||||
fileURLToPath(url: string | URL): string {
|
||||
return this.parent.fileURLToPath(url);
|
||||
}
|
||||
}
|
||||
|
||||
export class DefaultCompilerFileSystemAsyncCallbacks implements CompilerFileSystemAsyncCallbacks {
|
||||
|
|
@ -242,3 +271,15 @@ export class DefaultCompilerFileSystemAsyncCallbacks implements CompilerFileSyst
|
|||
return this.owner.resolveFilename(baseFilename, filename);
|
||||
}
|
||||
}
|
||||
|
||||
/** a CompilerCallbacks that applies the EventResolver to any reported message */
|
||||
export class ResolvingCompilerCallbacks extends DelegatingCompilerCallbacks {
|
||||
constructor(private eventResolver: EventResolver, options: CompilerCallbackOptions, parent: CompilerCallbacks) {
|
||||
super(options, parent);
|
||||
}
|
||||
|
||||
reportMessage(event: CompilerEvent) {
|
||||
this.eventResolver.resolve(event);
|
||||
this.parent.reportMessage(event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,23 @@
|
|||
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
|
||||
*/
|
||||
export interface CompilerEvent {
|
||||
filename?: string;
|
||||
/** line where a message applies */
|
||||
line?: number;
|
||||
/**
|
||||
* column where a message applies.
|
||||
*/
|
||||
column?: number;
|
||||
/**
|
||||
* offset where a message applies.
|
||||
* If set, encompasses line and column.
|
||||
*/
|
||||
offset?: number;
|
||||
code: number;
|
||||
message: string;
|
||||
/**
|
||||
|
|
@ -196,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 */
|
||||
|
|
@ -417,6 +450,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 => ({
|
||||
|
|
|
|||
|
|
@ -40,11 +40,15 @@ export {
|
|||
CompilerPathCallbacks,
|
||||
CompilerFileSystemCallbacksFolderEntry as FileSystemFolderEntry,
|
||||
DefaultCompilerFileSystemAsyncCallbacks,
|
||||
EventResolver,
|
||||
NullEventResolver,
|
||||
DelegatingCompilerCallbacks,
|
||||
ResolvingCompilerCallbacks,
|
||||
} from './compiler-callbacks.js';
|
||||
|
||||
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,
|
||||
|
|
@ -63,8 +67,9 @@ 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 } from './xml-utils.js';
|
||||
|
||||
export { KeymanXMLType, KeymanXMLWriter, KeymanXMLReader, KeymanXMLMetadata, XML_FILENAME_SYMBOL } from './xml-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';
|
||||
|
||||
|
|
@ -72,4 +77,4 @@ export { getFontFamily, getFontFamilySync } from './font-family.js';
|
|||
|
||||
export * as ValidIds from './valid-ids.js';
|
||||
|
||||
export * as ProjectLoader from './project-loader.js';
|
||||
export * as ProjectLoader from './project-loader.js';
|
||||
|
|
|
|||
124
developer/src/common/web/utils/src/line-utils.ts
Normal file
124
developer/src/common/web/utils/src/line-utils.ts
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by srl on 2025-04-03
|
||||
*
|
||||
* Abstraction for line number processing
|
||||
*/
|
||||
|
||||
import { CompilerEvent, EventResolver } from "./index.js";
|
||||
|
||||
/** line number with an optional column number */
|
||||
export interface LineColumn {
|
||||
line: number;
|
||||
column?: number;
|
||||
}
|
||||
|
||||
export type LineList = number[];
|
||||
|
||||
/** a LineFinder is able to convert from an offset to a line/column */
|
||||
export class LineFinder {
|
||||
private list: LineList;
|
||||
constructor(public text: string) {
|
||||
}
|
||||
|
||||
public getLineList(): LineList {
|
||||
if (!this.list) {
|
||||
this.list = LineFinder.textToLines(this.text);
|
||||
}
|
||||
return this.list;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert an offset into line:column
|
||||
* @param offset input offset into the text
|
||||
* @returns line:column information
|
||||
*/
|
||||
public findOffset(offset: number): LineColumn {
|
||||
return LineFinder.offsetToLineColumn(offset, this.getLineList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Preprocess text to turn it into arrays of line lengths.
|
||||
* This is effectively a 1-based line length, since line 0 has length of
|
||||
* 0.
|
||||
* Note that the fast-xml-parser converts \r\n to \n before processing,
|
||||
* and this function does as well. So, any offsets passed in will need to
|
||||
* have that taken into consideration.
|
||||
*/
|
||||
public static textToLines(text: string): LineList {
|
||||
return [
|
||||
0, // "line 0" is empty
|
||||
...text.replaceAll("\r\n", "\n").split("\n")
|
||||
.map(l => l.length + 1) // line length (counting the trailing newline)
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* convert a line number array to a line/column.
|
||||
* Note that the fast-xml-parser converts \r\n to \n before processing,
|
||||
* and this function does as well. So, any offsets passed in will need to
|
||||
* have that taken into consideration.
|
||||
*/
|
||||
public static offsetToLineColumn(offset: number, list: LineList): LineColumn {
|
||||
for (let line = 1; line < list.length; line++) { // 1-based (assume the first row is 0)
|
||||
if (list[line] > offset) {
|
||||
return { line, column: offset };
|
||||
}
|
||||
offset = offset - (list[line]); // count newline at end
|
||||
}
|
||||
// default: line 0, error
|
||||
return { line: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface for a class which can receive file contents,
|
||||
* keyed by the filename
|
||||
*/
|
||||
export interface FileConsumer {
|
||||
/**
|
||||
* @param filename name of the file
|
||||
* @param contents string contents of the file
|
||||
*/
|
||||
addFile(filename: string, contents: string): void;
|
||||
}
|
||||
|
||||
/** Cache of LineFinder elements, organized by filename */
|
||||
export class LineFinderCache implements FileConsumer {
|
||||
contentsCache: Map<string, LineFinder> = new Map();
|
||||
|
||||
/** add or update a source file */
|
||||
addFile(filename: string, contents: string): void {
|
||||
this.contentsCache.set(filename, new LineFinder(contents));
|
||||
}
|
||||
|
||||
getByFilename(filename: string): LineFinder | undefined {
|
||||
const lf = this.contentsCache.get(filename);
|
||||
return lf;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This EventResolver expands events that have a filename and offset set,
|
||||
* by looking up the event.offset in the LineFinder list.
|
||||
* The expanded event has the event.line and event.column fields set.
|
||||
*/
|
||||
export class LineFinderEventResolver implements EventResolver, FileConsumer {
|
||||
private lfcache = new LineFinderCache();
|
||||
|
||||
addFile(filename: string, contents: string): void {
|
||||
this.lfcache.addFile(filename, contents);
|
||||
}
|
||||
|
||||
/** resolve a CompilerEvent by expanding the line numbers */
|
||||
resolve(event: CompilerEvent) {
|
||||
if (event.offset && !event.line && event.filename) {
|
||||
const lf = this.lfcache.getByFilename(event.filename);
|
||||
const loc = lf.findOffset(event.offset);
|
||||
event.line = loc.line;
|
||||
event.column = loc.column;
|
||||
}
|
||||
return event;
|
||||
}
|
||||
}
|
||||
41
developer/src/common/web/utils/src/symbol-utils.ts
Normal file
41
developer/src/common/web/utils/src/symbol-utils.ts
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by srl on 2025-03-27
|
||||
*
|
||||
* 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'
|
||||
* @param onto object to copy onto
|
||||
* @param from source for symbols
|
||||
* @returns the onto object
|
||||
*/
|
||||
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];
|
||||
}
|
||||
return onto;
|
||||
}
|
||||
|
||||
/** use Object.entries to remove all symbols, recursively. */
|
||||
public static removeSymbols<T>(from: T): T {
|
||||
if (Array.isArray(from)) {
|
||||
return from.map(o => SymbolUtils.removeSymbols(o)) as T;
|
||||
}
|
||||
if (typeof from !== "object") return from;
|
||||
return Object.fromEntries(Object.entries(from).map(([k, v]) => ([k, SymbolUtils.removeSymbols(v)]))) as T;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -5,12 +5,14 @@
|
|||
*/
|
||||
import { SchemaValidators, util } from '@keymanapp/common-types';
|
||||
import { DeveloperUtilsMessages } from '../../developer-utils-messages.js';
|
||||
import { CompilerCallbacks } from "../../compiler-callbacks.js";
|
||||
import { CompilerCallbacks, EventResolver } from "../../compiler-callbacks.js";
|
||||
import { CompilerEvent } from "../../compiler-interfaces.js";
|
||||
import { LDMLKeyboardXMLSourceFile, LKImport, ImportStatus } from './ldml-keyboard-xml.js';
|
||||
import { constants } from '@keymanapp/ldml-keyboard-constants';
|
||||
import { LDMLKeyboardTestDataXMLSourceFile, LKTTest, LKTTests } from './ldml-keyboard-testdata-xml.js';
|
||||
import { KeymanXMLReader } from '@keymanapp/developer-utils';
|
||||
import boxXmlArray = util.boxXmlArray;
|
||||
import { LineFinderEventResolver } from '../../line-utils.js';
|
||||
import { XML_FILENAME_SYMBOL, KeymanXMLReader } from '../../xml-utils.js';
|
||||
|
||||
interface NameAndProps {
|
||||
'$'?: any; // content
|
||||
|
|
@ -25,28 +27,44 @@ export class LDMLKeyboardXMLSourceFileReaderOptions {
|
|||
localImportsPaths: string[];
|
||||
};
|
||||
|
||||
export class LDMLKeyboardXMLSourceFileReader {
|
||||
export class LDMLKeyboardXMLSourceFileReader implements EventResolver {
|
||||
/** for resolving messages involving line numbers */
|
||||
private eventResolver: LineFinderEventResolver = new LineFinderEventResolver();
|
||||
|
||||
constructor(private options: LDMLKeyboardXMLSourceFileReaderOptions, private callbacks : CompilerCallbacks) {
|
||||
}
|
||||
resolve(event: CompilerEvent): void {
|
||||
this.eventResolver.resolve(event);
|
||||
}
|
||||
|
||||
static get defaultImportsURL(): [string,string] {
|
||||
return ['../import/', import.meta.url];
|
||||
}
|
||||
|
||||
readImportFile(version: string, subpath: string): Uint8Array {
|
||||
const importPath = this.callbacks.resolveFilename(this.options.cldrImportsPath, `${version}/${subpath}`);
|
||||
return this.callbacks.loadFile(importPath);
|
||||
/** bottleneck for reading keyboard XML files */
|
||||
readFile(path: string): Uint8Array {
|
||||
const data = this.callbacks.loadFile(path);
|
||||
if (data) {
|
||||
this.eventResolver.addFile(path, new TextDecoder().decode(data));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
readLocalImportFile(path: string): Uint8Array {
|
||||
/** @returns [data, filename] */
|
||||
readImportFile(version: string, subpath: string): [Uint8Array, string] {
|
||||
const importPath = this.callbacks.resolveFilename(this.options.cldrImportsPath, `${version}/${subpath}`);
|
||||
return [this.readFile(importPath), importPath];
|
||||
}
|
||||
|
||||
readLocalImportFile(path: string): [Uint8Array, string] {
|
||||
// try each of the local imports paths
|
||||
for (const localPath of this.options.localImportsPaths) {
|
||||
const importPath = this.callbacks.path.join(localPath, path);
|
||||
if(this.callbacks.fs.existsSync(importPath)) {
|
||||
return this.callbacks.loadFile(importPath);
|
||||
return [this.readFile(importPath), importPath];
|
||||
}
|
||||
}
|
||||
return null; // was not able to load from any of the paths
|
||||
return [null, null]; // was not able to load from any of the paths
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -223,6 +241,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
return false;
|
||||
}
|
||||
let importData: Uint8Array;
|
||||
let importPath: string;
|
||||
|
||||
if (base === constants.cldr_import_base) {
|
||||
// CLDR import
|
||||
|
|
@ -235,10 +254,10 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
/** There's no data or DTD change in 45, 46, 46.1, 47 so map them all to 46 at present. */
|
||||
paths[0] = constants.cldr_version_latest;
|
||||
}
|
||||
importData = this.readImportFile(paths[0], paths[1]);
|
||||
[importData, importPath] = this.readImportFile(paths[0], paths[1]);
|
||||
} else {
|
||||
// local import
|
||||
importData = this.readLocalImportFile(path);
|
||||
[importData, importPath] = this.readLocalImportFile(path);
|
||||
}
|
||||
if (!importData || !importData.length) {
|
||||
this.callbacks.reportMessage(DeveloperUtilsMessages.Error_ImportReadFail({base, path, subtag}));
|
||||
|
|
@ -246,6 +265,7 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
}
|
||||
const importXml: any = this.loadUnboxed(importData); // TODO-LDML: have to load as any because it is an arbitrary part
|
||||
const importRootNode = importXml[subtag]; // e.g. <keys/>
|
||||
this.eventResolver.addFile(importPath, new TextDecoder().decode(importData)); // TODO: double decode
|
||||
|
||||
// importXml will have one property: the root element.
|
||||
if (!importRootNode) {
|
||||
|
|
@ -263,7 +283,11 @@ export class LDMLKeyboardXMLSourceFileReader {
|
|||
return false;
|
||||
}
|
||||
// Mark all children as an import
|
||||
subsubval.forEach(o => o[ImportStatus.import] = basePath);
|
||||
subsubval.forEach(o => {
|
||||
o[ImportStatus.import] = basePath;
|
||||
KeymanXMLReader.setMetaData(o, {[XML_FILENAME_SYMBOL as any]: importPath}); // mark overriding import path
|
||||
});
|
||||
|
||||
if (implied) {
|
||||
// mark all children as an implied import
|
||||
subsubval.forEach(o => o[ImportStatus.impliedImport] = basePath);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,13 @@
|
|||
* Abstraction for XML reading and writing
|
||||
*/
|
||||
|
||||
import { XMLParser, XMLBuilder, XmlBuilderOptions, X2jOptions } from 'fast-xml-parser';
|
||||
import { XMLParser, XMLBuilder, XMLMetaData, X2jOptions, XmlBuilderOptions } from 'fast-xml-parser';
|
||||
import { SymbolUtils } from "./symbol-utils.js";
|
||||
|
||||
/** Symbol giving the start offset, in chars, of the node */
|
||||
const XML_META_DATA_SYMBOL = XMLParser.getMetaDataSymbol();
|
||||
/** Symbol giving an override which file a node came from */
|
||||
export const XML_FILENAME_SYMBOL = Symbol("XML Filename");
|
||||
|
||||
export type KeymanXMLType =
|
||||
'keyboard3' // LDML <keyboard3>
|
||||
|
|
@ -48,6 +54,7 @@ const PARSER_OPTIONS: KeymanXMLParserOptionsBag = {
|
|||
// if we do need elements in the future, we'd check the preserve-space attribute here.
|
||||
return tagValue?.trim();
|
||||
},
|
||||
captureMetaData: true,
|
||||
trimValues: false, // preserve spaces, but see tagValueProcessor
|
||||
},
|
||||
'keyboardTest3': {
|
||||
|
|
@ -108,11 +115,54 @@ const GENERATOR_OPTIONS: KeymanXMLGeneratorOptionsBag = {
|
|||
},
|
||||
};
|
||||
|
||||
export interface KeymanXMLMetadata extends XMLMetaData {
|
||||
/** override of name of XML file */
|
||||
[XML_FILENAME_SYMBOL]?: string;
|
||||
}
|
||||
|
||||
/** wrapper for XML parsing support */
|
||||
export class KeymanXMLReader {
|
||||
public constructor(public type: KeymanXMLType) {
|
||||
}
|
||||
|
||||
/** Get metadata on a node if not already set */
|
||||
static getMetaData(o: any) : KeymanXMLMetadata {
|
||||
if(!o) return o;
|
||||
const metadata : KeymanXMLMetadata = o[XML_META_DATA_SYMBOL as any];
|
||||
return metadata;
|
||||
}
|
||||
|
||||
/** Set metadata if not already set */
|
||||
public static setMetaData(o: any, metadata: KeymanXMLMetadata) : KeymanXMLMetadata {
|
||||
let m : KeymanXMLMetadata = KeymanXMLReader.getMetaData(o);
|
||||
if (!m) {
|
||||
m = {};
|
||||
}
|
||||
// copy non-symbols
|
||||
m = {...metadata, ...m};
|
||||
// copy symbols
|
||||
SymbolUtils.copySymbols(m, metadata);
|
||||
o[XML_META_DATA_SYMBOL as any] = m;
|
||||
return m;
|
||||
}
|
||||
|
||||
/** set metadata on this and children with the default filename - if not already set */
|
||||
public static setDefaultFilename(data: any, filename: string) {
|
||||
if (!data || !filename) return;
|
||||
if (typeof data === 'object') {
|
||||
const m = KeymanXMLReader.getMetaData(data) || {};
|
||||
if (!m[XML_FILENAME_SYMBOL]) {
|
||||
(m as any)[XML_FILENAME_SYMBOL] = filename;
|
||||
KeymanXMLReader.setMetaData(data, m);
|
||||
}
|
||||
if (Array.isArray(data)) {
|
||||
data.forEach(e => KeymanXMLReader.setDefaultFilename(e, filename));
|
||||
} else for(const k of Object.keys(data)) {
|
||||
KeymanXMLReader.setDefaultFilename(data[k], filename);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** move `{ $abc: 4 }` into `{ $: { abc: 4 } }` */
|
||||
private static fixupDollarAttributes(data: any) : any {
|
||||
if (typeof data === 'object') {
|
||||
|
|
@ -131,9 +181,9 @@ export class KeymanXMLReader {
|
|||
}
|
||||
});
|
||||
if (attrs.length) {
|
||||
e.push(['$', Object.fromEntries(attrs)]);
|
||||
e.push(['$', SymbolUtils.copySymbols(Object.fromEntries(attrs), data)]);
|
||||
}
|
||||
return Object.fromEntries(e);
|
||||
return SymbolUtils.copySymbols(Object.fromEntries(e), data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
|
|
@ -169,7 +219,7 @@ export class KeymanXMLReader {
|
|||
}
|
||||
}
|
||||
});
|
||||
return Object.fromEntries(e);
|
||||
return SymbolUtils.copySymbols(Object.fromEntries(e), data);
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
|
|
@ -249,12 +299,11 @@ export class KeymanXMLReader {
|
|||
}
|
||||
|
||||
public parser() {
|
||||
let options = PARSER_OPTIONS[this.type];
|
||||
const options = PARSER_OPTIONS[this.type];
|
||||
if (!options) {
|
||||
/* c8 ignore next 1 */
|
||||
throw Error(`Internal error: unhandled XML type ${this.type}`);
|
||||
}
|
||||
options = Object.assign({}, options); // TODO: xml2js likes to mutate the options here. Shallow clone the object.
|
||||
return new XMLParser(options);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue