Merge pull request #14233 from keymanapp/feat/developer/kmc-convert-useArtifacts

feat(developer): kmc-convert: return data in-memory artifacts instead of writing to a file 😎
This commit is contained in:
SabineSIL 2025-08-21 17:53:53 +02:00 • committed by GitHub
commit cec9437a71
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 68 additions and 100 deletions

View file

@ -25,41 +25,40 @@ export class ConverterMessages {
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)}'.`
`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.`
this.ERROR_FileNotFound,
`Input filename '${def(o.inputFilename)} ' does not exist or could not be loaded.`,
);
static ERROR_UnableToRead = SevError | 0x0004;
static Error_UnableToRead = (o: { inputFilename: string; }) => m(
this.ERROR_UnableToRead, `Input file '${def(o.inputFilename)}
' could not be read.`
this.ERROR_UnableToRead,
`Input file '${def(o.inputFilename)} ' could not be read.`,
);
static ERROR_UnableToConvert = SevError | 0x0005;
static Error_UnableToConvert = (o: { inputFilename: string; }) => m(
this.ERROR_UnableToConvert, `Input file '${def(o.inputFilename)}
' could not be converted.`
this.ERROR_UnableToConvert,
`Input file '${def(o.inputFilename)} ' could not be converted.`,
);
static ERROR_UnableToWrite = SevError | 0x0006;
static Error_UnableToWrite = (o: { outputFilename: string; }) => m(
this.ERROR_UnableToWrite, `Output file for '${def(o.outputFilename)}
' could not be written.`
this.ERROR_UnableToWrite,
`Output file for '${def(o.outputFilename)} ' could not be written.`,
);
static ERROR_UnsupportedCharactersDetected = SevError | 0x0007;
static Error_UnsupportedCharactersDetected = (o: { inputFilename: string, keymap_index: string, key: string, output: string; }) => m(
this.ERROR_UnsupportedCharactersDetected, `Input file ${def(o.inputFilename)} contains unsupported character ('${def(o.output)}') on key (${def(o.key)}) at keyMap index ${def(o.keymap_index)}`
this.ERROR_UnsupportedCharactersDetected,
`Input file ${def(o.inputFilename)} contains unsupported character ('${def(o.output)}') on key ( ${def(o.key)}) at keyMap index ${def(o.keymap_index)}`,
);
static ERROR_InvalidFile = SevError | 0x0008;
static Error_InvalidFile = (o: { errorText: string; }) => m(
this.ERROR_InvalidFile, `The source file has an invalid structure: ${def(o.errorText)}`
);
this.ERROR_InvalidFile, `The source file has an invalid structure: ${def(o.errorText)}`);
}

View file

@ -83,7 +83,8 @@ export class Converter implements KeymanCompiler {
const converter = new ConverterClass(this.callbacks, converterOptions);
const artifacts = await converter.run(inputFilename, outputFilename);
// Note: any subsequent errors in conversion will have been reported by the converter
return artifacts ? { artifacts } : null;
//return artifacts ? { artifacts } : null;
return artifacts ? artifacts : null;
}
/**

View file

@ -7,19 +7,32 @@
*
*/
import { CompilerCallbacks, CompilerOptions } from "@keymanapp/developer-utils";
import { ConverterToKmnArtifacts } from "../converter-artifacts.js";
import { CompilerCallbacks, CompilerOptions, KeymanCompilerResult, } from "@keymanapp/developer-utils";
import { KmnFileWriter } from './kmn-file-writer.js';
import { KeylayoutFileReader } from './keylayout-file-reader.js';
import { ConverterMessages } from '../converter-messages.js';
import { ConverterArtifacts } from "../converter-artifacts.js";
import { ConverterToKmnArtifacts } from "../converter-artifacts.js"
import { KeylayoutXMLSourceFile } from '../../../common/web/utils/src/types/keylayout/keylayout-xml.js';
/**
* Object holding all important data for the conversion between
* input (*.keylayout) format and output (*.kmn) format.
* It contains input and output filenames, an array of all used modifiers
* and all preprocessed key rules for up to 3 key/modifier combinations.
*/
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;
};
export interface ConverterToKmnResult extends ConverterResult {
/**
* Internal in-memory build artifacts from a successful compilation. Caller
* can write these to disk with {@link Converter.write}
*/
artifacts: ConverterToKmnArtifacts;
};
export interface convert_object {
keylayout_filename: string,
kmn_filename: string,
@ -51,14 +64,15 @@ export class KeylayoutToKmnConverter {
* @param outputFilename the resulting keyman .kmn-file
* @return null on success
*/
async run(inputFilename: string, outputFilename?: string): Promise<ConverterToKmnArtifacts> {
async run(inputFilename: string, outputFilename?: string): Promise<ConverterToKmnResult> {
if (!inputFilename) {
this.callbacks.reportMessage(ConverterMessages.Error_FileNotFound({ inputFilename }));
return null;
}
outputFilename = outputFilename ?? inputFilename.replace(/\.keylayout$/, '.kmn');
const KeylayoutReader = new KeylayoutFileReader(this.callbacks/*, this.options*/);
const jsonO: KeylayoutXMLSourceFile = KeylayoutReader.read(inputFilename);
@ -76,8 +90,6 @@ export class KeylayoutToKmnConverter {
return null;
}
outputFilename = outputFilename ?? inputFilename.replace(/\.keylayout$/, '.kmn');
const outArray: convert_object = await this.convert(jsonO, outputFilename);
if (!outArray) {
this.callbacks.reportMessage(ConverterMessages.Error_UnableToConvert({ inputFilename }));
@ -86,15 +98,27 @@ export class KeylayoutToKmnConverter {
const kmnFileWriter = new KmnFileWriter(this.callbacks, this.options);
const out_text_ok: boolean = kmnFileWriter.write(outArray);
// _S2 still write to file - will be removed later
const out_text_ok: boolean = kmnFileWriter.writeToFile(outArray);
if (!out_text_ok) {
this.callbacks.reportMessage(ConverterMessages.Error_UnableToWrite({ outputFilename }));
return null;
}
return null;
}
// write to object/ConverterToKmnResult
const out_Uint8: Uint8Array = kmnFileWriter.write(outArray);
const Result_toBeReturned: ConverterToKmnResult = {
artifacts: {
kmn: { data: out_Uint8, filename: outputFilename }
}
};
if (!out_Uint8) {
this.callbacks.reportMessage(ConverterMessages.Error_UnableToWrite({ outputFilename }));
return null;
}
return Result_toBeReturned;
}
/**
* @brief member function to read filename and behaviour of a json object into a convert_object

View file

@ -23,7 +23,7 @@ export class KmnFileWriter {
* @param outputfilename the file that will be written; if no outputfilename is given an outputfilename will be created from data_ukelele.keylayout_filename
* @return true if data has been written; false if not
*/
public write(data_ukelele: convert_object): boolean {
public writeToFile(data_ukelele: convert_object): boolean {
let data: string = "\n";
@ -37,29 +37,12 @@ export class KmnFileWriter {
this.callbacks.fs.writeFileSync(data_ukelele.kmn_filename, new TextEncoder().encode(data));
return true;
} catch (err) {
this.callbacks.reportMessage(ConverterMessages.Error_OutputFilenameIsRequired());
this.callbacks.reportMessage(ConverterMessages.Error_UnableToWrite({outputFilename: data_ukelele.kmn_filename}));
return false;
}
}
public writeToString(data_ukelele: convert_object): string {
let data: string = "\n";
// add top part of kmn file: STORES
data += this.write_KmnFileHeader(data_ukelele);
// add bottom part of kmn file: RULES
data += this.writeData_Rules(data_ukelele);
try {
return data;
} catch (err) {
this.callbacks.reportMessage(ConverterMessages.Error_OutputFilenameIsRequired());
return null;
}
}
public writeToUint8Array(data_ukelele: convert_object): Uint8Array {
public write(data_ukelele: convert_object): Uint8Array {
let data: string = "\n";
// add top part of kmn file: STORES
@ -71,7 +54,7 @@ export class KmnFileWriter {
try {
return new TextEncoder().encode(data);
} catch (err) {
this.callbacks.reportMessage(ConverterMessages.Error_OutputFilenameIsRequired());
this.callbacks.reportMessage(ConverterMessages.Error_UnableToWrite({outputFilename: data_ukelele.kmn_filename}));
return null;
}
}

View file

@ -22,7 +22,7 @@ describe('KmnFileWriter', function () {
compilerTestCallbacks.clear();
});
describe("write() ", function () {
describe("writeToFile() ", function () {
const inputFilename = makePathToFixture('../data/Test.keylayout');
const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions);
const sut_r = new KeylayoutFileReader(compilerTestCallbacks);
@ -35,59 +35,19 @@ describe('KmnFileWriter', function () {
const read_unavailable = sut_r.read(inputFilename_unavailable);
const converted_unavailable = sut.convert_bound.convert(read_unavailable, inputFilename_unavailable.replace(/\.keylayout$/, '.kmn'));
it('write() should return true (no error) if written', async function () {
const result = sut_w.write(converted);
it('writeToFile()() should return true (no error) if written', async function () {
const result = sut_w.writeToFile(converted);
assert.isTrue(result);
});
it('write() should return false if no inputfile', async function () {
const result = sut_w.write(converted_unavailable);
it('writeToFile()() should return false if no inputfile', async function () {
const result = sut_w.writeToFile(converted_unavailable);
assert.isFalse(result);
});
});
describe("writeToString() ", function () {
const inputFilename = makePathToFixture('../data/Test.keylayout');
const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions);
const sut_r = new KeylayoutFileReader(compilerTestCallbacks);
const sut_w = new KmnFileWriter(compilerTestCallbacks, compilerTestOptions);
const read = sut_r.read(inputFilename);
const converted = sut.convert_bound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn'));
const out_expected_first: string = "c ..................................................................................................................\n"
+ "c ..................................................................................................................\n"
+ "c Keyman keyboard generated by kmn-convert version: " + KEYMAN_VERSION.VERSION + "\n"
+ "c from Ukelele file: ";
const out_expected_last: string = "\n"
+ "c ..................................................................................................................\n"
+ "c ..................................................................................................................\n"
+ "\n"
+ "store(&TARGETS) 'desktop'\n"
+ "\n"
+ "begin Unicode > use(main)\n\n"
+ "group(main) using keys\n\n"
+ "\n";
// empty convert_object from unavailable file name
const inputFilename_unavailable = makePathToFixture('../data/X.keylayout');
const read_unavailable = sut_r.read(inputFilename_unavailable);
const converted_unavailable = sut.convert_bound.convert(read_unavailable, inputFilename_unavailable.replace(/\.keylayout$/, '.kmn'));
it('writeToString() should return result', async function () {
const result = sut_w.writeToString(converted);
assert.isNotNull(result);
});
it('writeToString() should return header in case of missing inputfile', async function () {
const result = sut_w.writeToString(converted_unavailable);
assert.equal(result, ("\n" + out_expected_first + out_expected_last));
});
});
describe("writeToUint8Array() ", function () {
describe("write()() ", function () {
const inputFilename = makePathToFixture('../data/Test.keylayout');
const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions);
const sut_r = new KeylayoutFileReader(compilerTestCallbacks);
@ -116,13 +76,14 @@ describe('KmnFileWriter', function () {
const inputFilename_unavailable = makePathToFixture('../data/X.keylayout');
const read_unavailable = sut_r.read(inputFilename_unavailable);
const converted_unavailable = sut.convert_bound.convert(read_unavailable, inputFilename_unavailable.replace(/\.keylayout$/, '.kmn'));
it('writeToUint8Array() should return header in case of missing inputfile', async function () {
const result = sut_w.writeToUint8Array(converted_unavailable);
it('write()() should return header in case of missing inputfile', async function () {
const result = sut_w.write(converted_unavailable);
assert.equal(new TextDecoder().decode(result), ("\n" + out_expected_first + out_expected_last));
});
it('writeToUint8Array() should return result', async function () {
const result = sut_w.writeToUint8Array(converted);
it('write()() should return result', async function () {
const result = sut_w.write(converted);
assert.isNotNull(result);
});
});