Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

epic: kmc-convert 😎 #12191

Draft
wants to merge 17 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
a201556
chore(developer): establish kmc-convert epic
mcdurdin Aug 15, 2024
c600b73
feat(developer): introduce kmc-convert
mcdurdin Aug 15, 2024
0c18ca0
chore(developer): cleanup dependencies in kmc-convert
mcdurdin Aug 15, 2024
a766e44
chore(developer): reduce kmc coverage threshold to 45%
mcdurdin Aug 15, 2024
d968439
chore(developer): add api-extractor config for kmc-convert
mcdurdin Aug 16, 2024
45862d2
chore(developer): fixup api-extractor config
mcdurdin Aug 16, 2024
a53c642
Merge pull request #12192 from keymanapp/feat/developer/introduce-kmc…
mcdurdin Aug 16, 2024
c1bfcac
Merge pull request #12254 from keymanapp/chore/merge-master-into-kmc-…
mcdurdin Aug 22, 2024
48672f4
Merge pull request #12318 from keymanapp/chore/merge-master-into-kmc-…
mcdurdin Aug 30, 2024
b4ea167
Merge pull request #12412 from keymanapp/chore/merge-master-into-kmc-…
mcdurdin Sep 13, 2024
cb72573
Merge branch 'epic/kmc-convert' into chore/merge-master-into-kmc-convert
mcdurdin Oct 10, 2024
f669c88
Merge pull request #12531 from keymanapp/chore/merge-master-into-kmc-…
mcdurdin Oct 11, 2024
9d4c103
Merge branch 'epic/kmc-convert' into chore/merge-master-into-kmc-convert
mcdurdin Oct 25, 2024
8cada6b
Merge pull request #12576 from keymanapp/chore/merge-master-into-kmc-…
mcdurdin Oct 25, 2024
ecdcd41
chore: Merge remote-tracking branch 'origin/epic/kmc-convert' into ch…
mcdurdin Nov 8, 2024
ecbe2f0
chore: npm install
mcdurdin Nov 10, 2024
29f28a0
Merge pull request #12651 from keymanapp/chore/merge-master-into-kmc-…
mcdurdin Nov 11, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions developer/src/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ node-based next generation compiler, hosts kmc, (and legacy kmlmc, kmlmp)

File analysis tools for Keyman files.

### kmc-convert - Keyboard conversion tools

Tools for converting keyboard source files between various formats.

### kmc-copy - Project copying and renaming tools

Tools to copy and rename Keyman keyboard files and projects
Expand Down
1 change: 1 addition & 0 deletions developer/src/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ builder_describe \
":kmcmplib Compiler - .kmn compiler" \
":kmc-analyze Compiler - Analysis Tools" \
":kmc-copy Compiler - Project Copying and Renaming Tools" \
":kmc-convert Compiler - Keyboard Conversion Tools" \
":kmc-generate Compiler - Generation Tools" \
":kmc-keyboard-info Compiler - .keyboard_info Module" \
":kmc-kmn Compiler - .kmn to .kmx and .js Keyboard Module" \
Expand Down
4 changes: 4 additions & 0 deletions developer/src/common/web/utils/src/compiler-interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -273,6 +273,10 @@ export enum CompilerErrorNamespace {
* kmc-copy 0xB000…0xBFFF
*/
Copier = 0xB000,
/**
* kmc-convert 0xC000…0xCFFF
*/
Converter = 0xC000,
};

type CompilerErrorSeverityOverride = CompilerErrorSeverity | 'disable';
Expand Down
11 changes: 11 additions & 0 deletions developer/src/kmc-convert/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module.exports = {
parserOptions: {
project: ["./tsconfig.json", "./test/tsconfig.json"],
},
ignorePatterns: ["test/fixtures/**/*"],
overrides: [
{
files:"src/**/*.ts",
}
],
};
6 changes: 6 additions & 0 deletions developer/src/kmc-convert/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# Keyman Developer - kmc-convert

This package provides keyboard conversion tools. It can be used from the
command line with [@keymanapp/kmc](https://npmjs.com/package/@keymanapp/kmc).

* [API Reference](https://help.keyman.com/developer/current-version/reference/api/kmc-convert)
49 changes: 49 additions & 0 deletions developer/src/kmc-convert/build.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
#!/usr/bin/env bash
## START STANDARD BUILD SCRIPT INCLUDE
# adjust relative paths as necessary
THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")"
. "${THIS_SCRIPT%/*}/../../../resources/build/builder.inc.sh"
## END STANDARD BUILD SCRIPT INCLUDE

. "$KEYMAN_ROOT/resources/build/build-utils-ci.inc.sh"
. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh"

builder_describe "Keyman kmc-convert keyboard conversion tools module" \
"@/developer/src/common/web/utils" \
"@/developer/src/common/web/test-helpers" \
"configure" \
"build" \
"api analyze API and prepare API documentation" \
"clean" \
"test" \
"publish publish to npm" \
"--npm-publish+ For publish, do a npm publish, not npm pack (only for CI)" \
"--dry-run,-n don't actually publish, just dry run"

builder_describe_outputs \
configure /node_modules \
build /developer/src/kmc-convert/build/src/main.js \
api /developer/build/api/kmc-convert.api.json

builder_parse "$@"

#-------------------------------------------------------------------------------------------------------------------

builder_run_action clean rm -rf ./build/ ./tsconfig.tsbuildinfo
builder_run_action configure verify_npm_setup
builder_run_action build tsc --build
builder_run_action api api-extractor run --local --verbose

do_test() {
eslint .
cd test
tsc -b
cd ..
readonly C8_THRESHOLD=20
c8 -skip-full --reporter=lcov --reporter=text --lines $C8_THRESHOLD --statements $C8_THRESHOLD --branches $C8_THRESHOLD --functions $C8_THRESHOLD mocha "${builder_extra_params[@]}"
builder_echo warning "Coverage thresholds are currently $C8_THRESHOLD%, which is lower than ideal."
builder_echo warning "Please increase threshold in build.sh as test coverage improves."
}

builder_run_action test do_test
builder_run_action publish builder_publish_npm
12 changes: 12 additions & 0 deletions developer/src/kmc-convert/config/api-extractor.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
/**
* Config file for API Extractor. For more info, please visit: https://api-extractor.com
*/
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"extends": "../../../config/api-extractor.base.json",
"mainEntryPointFilePath": "<projectFolder>/build/src/main.d.ts",
"docModel": {
"enabled": true,
"projectFolderUrl": "http://github.com/keymanapp/keyman/tree/master/developer/src/kmc-convert"
}
}
67 changes: 67 additions & 0 deletions developer/src/kmc-convert/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
{
"name": "@keymanapp/kmc-convert",
"description": "Keyman Developer keyboard conversion tools",
"keywords": [
"keyboard",
"keyman",
"ldml",
"unicode",
"xkb",
"keylayout",
"inkey",
"kmn",
"msklc"
],
"type": "module",
"exports": {
".": "./build/src/main.js"
},
"files": [
"/build/src/"
],
"scripts": {
"build": "gosh ./build.sh build",
"test": "gosh ./build.sh test"
},
"author": "Sabine Schmitt",
"license": "MIT",
"bugs": {
"url": "https://github.com/keymanapp/keyman/issues"
},
"dependencies": {
"@keymanapp/developer-utils": "*"
},
"devDependencies": {
"@keymanapp/developer-test-helpers": "*",
"@keymanapp/resources-gosh": "*",
"@types/mocha": "^5.2.7",
"@types/node": "^20.4.1",
"@types/semver": "^7.3.12",
"c8": "^7.12.0",
"chalk": "^2.4.2",
"mocha": "^8.4.0",
"typescript": "^5.4.5"
},
"mocha": {
"spec": "build/test/**/test-*.js",
"require": [
"source-map-support/register"
]
},
"c8": {
"all": true,
"src": [
"src/"
],
"exclude-after-remap": true,
"exclude": [
"test/",
"src/converter-options.ts",
"src/converter-artifacts.ts"
]
},
"repository": {
"type": "git",
"url": "git+https://github.com/keymanapp/keyman.git"
}
}
27 changes: 27 additions & 0 deletions developer/src/kmc-convert/src/converter-artifacts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Output artifacts available from kmc-convert
*/
import { KeymanCompilerArtifactOptional, KeymanCompilerArtifacts } from '@keymanapp/developer-utils';

export interface ConverterArtifacts extends KeymanCompilerArtifacts { }

/**
* @public
* Internal in-memory build artifacts from a successful compilation
*/
export interface ConverterToKmnArtifacts extends ConverterArtifacts {
/**
* Source keyboard filedata and filename
*/
kmn?: KeymanCompilerArtifactOptional;
/**
* Source on screen keyboard filedata and filename
*/
kvks?: KeymanCompilerArtifactOptional;
/**
* Source touch keyboard filedata and filename
*/
keymanTouchLayout?: KeymanCompilerArtifactOptional;
};
22 changes: 22 additions & 0 deletions developer/src/kmc-convert/src/converter-class-factory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Lists all the available converters and finds matching converter
*/
import { KeylayoutToKmnConverter } from './keylayout-to-kmn/keylayout-to-kmn-converter.js';

const converters = [
KeylayoutToKmnConverter,
];

export class ConverterClassFactory {
static find(inputFilename: string, outputFilename: string) {

const converter = converters.find(c =>
inputFilename.endsWith(c.INPUT_FILE_EXTENSION) &&
outputFilename.endsWith(c.OUTPUT_FILE_EXTENSION)
);

return converter;
}
}
30 changes: 30 additions & 0 deletions developer/src/kmc-convert/src/converter-messages.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Converter messages
*/
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def } from '@keymanapp/developer-utils';

const Namespace = CompilerErrorNamespace.Converter;
// const SevInfo = CompilerErrorSeverity.Info | Namespace;
// const SevHint = CompilerErrorSeverity.Hint | Namespace;
// const SevWarn = CompilerErrorSeverity.Warn | Namespace;
const SevError = CompilerErrorSeverity.Error | Namespace;
// const SevFatal = CompilerErrorSeverity.Fatal | Namespace;

/**
* @internal
*/
export class ConverterMessages {
static ERROR_OutputFilenameIsRequired = SevError | 0x0001;
static Error_OutputFilenameIsRequired = () =>
m(this.ERROR_OutputFilenameIsRequired, `An output filename is required for keyboard conversion.`);

static ERROR_NoConverterFound = SevError | 0x0002;
static Error_NoConverterFound = (o:{inputFilename: string, outputFilename: string}) =>
m(this.ERROR_NoConverterFound, `No converter is available that can convert from '${def(o.inputFilename)}' to '${def(o.outputFilename)}'.`);

static ERROR_FileNotFound = SevError | 0x0003;
static Error_FileNotFound = (o:{inputFilename: string}) =>
m(this.ERROR_FileNotFound, `Input filename '${def(o.inputFilename)}' does not exist or could not be loaded.`);
}
17 changes: 17 additions & 0 deletions developer/src/kmc-convert/src/converter-options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Converter options
*/
import { CompilerOptions } from "@keymanapp/developer-utils";

/**
* @public
* Options for the keyboard converter
*/
export interface ConverterOptions extends CompilerOptions {
/**
* Fail if the keyboard conversion is not 100% complete
*/
failIfIncomplete?: boolean;
};
111 changes: 111 additions & 0 deletions developer/src/kmc-convert/src/converter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
/*
* Keyman is copyright (C) SIL International. MIT License.
*
* Infrastructure for keyboard source file conversion tools
*/
import {
CompilerCallbacks,
CompilerOptions,
defaultCompilerOptions,
KeymanCompiler,
KeymanCompilerResult,
} from "@keymanapp/developer-utils";
import { ConverterClassFactory } from './converter-class-factory.js';
import { ConverterArtifacts } from "./converter-artifacts.js";
import { ConverterMessages } from "./converter-messages.js";

export interface ConverterResult extends KeymanCompilerResult {
/**
* Internal in-memory build artifacts from a successful compilation. Caller
* can write these to disk with {@link Converter.write}
*/
artifacts: ConverterArtifacts;
};

/**
* @public
* Converts keyboards between different source file formats. The
* compiler does not read or write from filesystem or network directly, but
* relies on callbacks for all external IO.
*/
export class Converter implements KeymanCompiler {
private callbacks: CompilerCallbacks;
private options: CompilerOptions;

/**
* Initialize the converter. Copies options.
* @param callbacks - Callbacks for external interfaces, including message
* reporting and file io
* @param options - Compiler options
* @returns false if initialization fails
*/
async init(callbacks: CompilerCallbacks, options: CompilerOptions): Promise<boolean> {
this.options = { ...options };
this.callbacks = callbacks;
return true;
}

/**
* Converts a keyboard source file to another format. Returns an object
* containing source artifacts on success. The files are passed in by name,
* and the compiler will use callbacks as passed to the {@link Converter.init}
* function to read any input files by disk.
* @param infile - Path to source file.
* @param outfile - Path to output file. The file will not be written to, but
* will be included in the result for use by
* {@link Converter.write}.
* @returns Source artifacts on success, null on failure.
*/
async run(inputFilename: string, outputFilename?: string): Promise<ConverterResult> {

const converterOptions: CompilerOptions = {
...defaultCompilerOptions,
...this.options,
};

if(!outputFilename) {
this.callbacks.reportMessage(ConverterMessages.Error_OutputFilenameIsRequired());
return null;
}

const ConverterClass = ConverterClassFactory.find(inputFilename, outputFilename);
if(!ConverterClass) {
this.callbacks.reportMessage(ConverterMessages.Error_NoConverterFound({inputFilename, outputFilename}));
return null;
}

const binaryData = this.callbacks.loadFile(inputFilename);
if(!binaryData) {
this.callbacks.reportMessage(ConverterMessages.Error_FileNotFound({inputFilename}));
return null;
}

const converter = new ConverterClass(this.callbacks, converterOptions);
const artifacts = await converter.run(inputFilename, outputFilename, binaryData);
// Note: any subsequent errors in conversion will have been reported by the converter
return artifacts ? { artifacts } : null;
}

/**
* Write artifacts from a successful compile to disk, via callbacks methods.
* The artifacts written may include:
*
* - .kmn file - source keyboard used by Keyman on desktop platforms
* - .kvks file - source on screen keyboard used by Keyman on desktop platforms
* - .keyman-touch-layout file - source touch layout keyboard for touch platforms
* - other keyboard source files as implemented
*
* @param artifacts - object containing artifact data to write out
* @returns true on success
*/
async write(artifacts: ConverterArtifacts): Promise<boolean> {
for(const key of Object.keys(artifacts)) {
if(artifacts[key]) {
this.callbacks.fs.writeFileSync(artifacts[key].filename, artifacts[key].data);
}
}

return true;
}
}

Loading
Loading