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 14 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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-keyboard-info - Keyboard Info Compiler

Builds .keyboard_info files for use on the Keyman Cloud keyboard repository
Expand Down
1 change: 1 addition & 0 deletions developer/src/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ builder_describe \
":help Online documentation" \
":kmcmplib Compiler - .kmn compiler" \
":kmc-analyze Compiler - Analysis Tools" \
":kmc-convert Compiler - Keyboard Conversion Tools" \
":kmc-keyboard-info Compiler - .keyboard_info Module" \
":kmc-kmn Compiler - .kmn to .kmx and .js Keyboard Module" \
":kmc-ldml Compiler - LDML 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 @@ -263,6 +263,10 @@ export enum CompilerErrorNamespace {
* kmc-keyboard-info 0x9000…0x9FFF
*/
KeyboardInfoCompiler = 0x9000,
/**
* kmc-convert 0xA000…0xAFFF
*/
Converter = 0xA000,
};

/**
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