feat(developer): solve merge conflicts

This commit is contained in:
Sabine 2025-08-25 10:37:08 +02:00
parent 12b17e45ee
commit ff4e7c98f3
5 changed files with 84 additions and 136 deletions

View file

@ -6,7 +6,7 @@
import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m, CompilerMessageDef as def } from '@keymanapp/developer-utils';
const Namespace = CompilerErrorNamespace.Converter;
const SevInfo = CompilerErrorSeverity.Info | Namespace;
const SevInfo = CompilerErrorSeverity.Info | Namespace;
// const SevHint = CompilerErrorSeverity.Hint | Namespace;
// const SevWarn = CompilerErrorSeverity.Warn | Namespace;
const SevError = CompilerErrorSeverity.Error | Namespace;
@ -53,8 +53,11 @@ export class ConverterMessages {
);
static INFO_UnsupportedCharactersDetected = SevInfo | 0x0007;
static Info_UnsupportedCharactersDetected = (o: { inputFilename: string, keymap_index: string, key: string,KeyName:string, output: string; }) => m(
this.INFO_UnsupportedCharactersDetected, `INFO: Input file ${def(o.inputFilename)} contains unsupported character '${def(o.output)}' at keyMap index ${def(o.keymap_index)} on Keycode ${def(o.key)} (${def(o.KeyName)})`
static Info_UnsupportedCharactersDetected = (o: { inputFilename: string, keymap_index: string, key: string, KeyName: string, output: string; }) => m(
this.INFO_UnsupportedCharactersDetected, `INFO: Input file ${def(o.inputFilename)}
contains unsupported character '${def(o.output)}
' at keyMap index ${def(o.keymap_index)}
on Keycode ${def(o.key)} (${def(o.KeyName)})`
);
static ERROR_InvalidFile = SevError | 0x0008;

View file

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

View file

@ -7,19 +7,34 @@
*
*/
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 { KeylayoutXMLSourceFile } from '@keymanapp/developer-utils';
import { ConverterArtifacts } from "../converter-artifacts.js";
import { ConverterToKmnArtifacts } from "../converter-artifacts.js";
import { KeylayoutXMLSourceFile } from '../../../common/web/utils/src/types/keylayout/keylayout-xml.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;
};
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;
};
/**
* 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 ProcesData {
keylayout_filename: string,
kmn_filename: string,
@ -51,12 +66,12 @@ export interface ActionStateOutput {
*/
export function find_usedKeysCount(data: any, pos: number): number {
let usedKeyCount = KeylayoutToKmnConverter.MAX_KEY_COUNT;
if (data.keyboard.keyMapSet[0].keyMap[pos].key.length < usedKeyCount ) {
// set the max to n-1 (keys are zero indexed )
usedKeyCount = data.keyboard.keyMapSet[0].keyMap[pos].key.length - 1;
let usedKeyCount = KeylayoutToKmnConverter.MAX_KEY_COUNT;
if (data.keyboard.keyMapSet[0].keyMap[pos].key.length < usedKeyCount) {
// set max to n-1 (keys are zero indexed )
usedKeyCount = data.keyboard.keyMapSet[0].keyMap[pos].key.length - 1;
}
return usedKeyCount ;
return usedKeyCount;
}
export class KeylayoutToKmnConverter {
@ -83,8 +98,7 @@ 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 }));
@ -123,16 +137,21 @@ export class KeylayoutToKmnConverter {
const kmnFileWriter = new KmnFileWriter(this.callbacks, this.options);
const out_text_ok: boolean = kmnFileWriter.write(outArray);
if (!out_text_ok) {
// 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 null;
return Result_toBeReturned;
}
/**
* @brief member function to read filename and behaviour of a json object into a ProcesData
* @param jsonObj containing filename, behaviour and rules of a json object
@ -148,10 +167,8 @@ export class KeylayoutToKmnConverter {
arrayOf_Modifiers: [],
arrayOf_Rules: []
};
//_S2 do I need to "validate again here?"
if ((jsonObj !== null) && (jsonObj.hasOwnProperty("keyboard"))) {
//if ((jsonObj !== null) ) {
if ((jsonObj !== null) && (jsonObj.hasOwnProperty("keyboard"))) {
data_object.keylayout_filename = outputfilename.replace(/\.kmn$/, '.keylayout');
data_object.kmn_filename = outputfilename;
data_object.arrayOf_Modifiers = modifierBehavior; // ukelele uses behaviours e.g. 18 modifiersCombinations in 8 KeyMapSelect(behaviors)

View file

@ -23,12 +23,12 @@ 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: ProcesData): boolean {
public writeToFile(data_ukelele: ProcesData): boolean {
let data: string = "\n";
// add top part of kmn file: STORES
data += this.writeData_Stores(data_ukelele);
data += this.write_KmnFileHeader(data_ukelele);
// add bottom part of kmn file: RULES
data += this.writeData_Rules(data_ukelele);
@ -37,33 +37,21 @@ 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: ProcesData): string {
/**
* @brief member function to write data from object to a Uint8Array
* @param data_ukelele the array holding all keyboard data
* @return a Uint8Array holding data
*/
public write(data_ukelele: ProcesData): Uint8Array {
let data: string = "\n";
// add top part of kmn file: STORES
data += this.writeData_Stores(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: ProcesData): Uint8Array {
let data: string = "\n";
// add top part of kmn file: STORES
data += this.writeData_Stores(data_ukelele);
data += this.write_KmnFileHeader(data_ukelele);
// add bottom part of kmn file: RULES
data += this.writeData_Rules(data_ukelele);
@ -71,17 +59,17 @@ 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;
}
}
/**
* @brief member function to create data for stores that will be printed to the resulting kmn file
* @brief member function to create data for the header (stores) that will be printed to the resulting kmn file
* @param data_ukelele an object containing all data read from a .keylayout file
* @return string - all stores to be printed
*/
public writeData_Stores(data_ukelele: ProcesData): string {
public write_KmnFileHeader(data_ukelele: ProcesData): string {
let data: string = "";
@ -114,6 +102,9 @@ export class KmnFileWriter {
let data: string = "";
// filter array of all rules and remove duplicates
// during the process of creating Rule[], duplicate rules might occur
// (e.g. when in a keylayout file the same modifiers occur in several behaviors thus producing the same rules).
// This is to filter out those duplicate Rule objects
const unique_data_Rules: Rule[] = data_ukelele.arrayOf_Rules.filter((curr) => {
return (!(curr.output === new TextEncoder().encode("") || curr.output === undefined)
&& (curr.key !== "")
@ -416,7 +407,8 @@ export class KmnFileWriter {
/**
* @brief member function to review rules for acceptable modifiers, duplicate or ambiguous rules and return an array containing possible warnings.
* Definition of comparisons e.g. 1-1, 2-4, 6-6
* Keyman can not handle duplicate rules so we need to make sure a rule is written only once by either omitting a duplicate rule or commenting out an ambiguous rule.
* Omitting rules and definition of comparisons e.g. 1-1, 2-4, 6-6
* see https://docs.google.com/document/d/12J3NGO6RxIthCpZDTR8FYSRjiMgXJDLwPY2z9xqKzJ0/edit?tab=t.0#heading=h.pcz8rjyrl5ug
* @param rule : Rule[] - an array of all rules
* @param index the index of a rule in array[rule]

View file

@ -22,6 +22,7 @@ describe('KmnFileWriter', function () {
compilerTestCallbacks.clear();
});
describe("write() ", function () {
const inputFilename = makePathToFixture('../data/Test.keylayout');
const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions);
@ -30,77 +31,12 @@ describe('KmnFileWriter', function () {
const read = sut_r.read(inputFilename);
const converted = sut.convert_bound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn'));
// empty ProcesData 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('write() should return true (no error) if written', async function () {
const result = sut_w.write(converted);
assert.isTrue(result);
});
it('write() should return false if no inputfile', async function () {
const result = sut_w.write(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 ProcesData 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 () {
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 =
const out_expected: 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 from Ukelele file: "
+ "\n"
+ "c ..................................................................................................................\n"
+ "c ..................................................................................................................\n"
+ "\n"
@ -115,13 +51,13 @@ 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);
assert.equal(new TextDecoder().decode(result), ("\n" + out_expected_first + out_expected_last));
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));
});
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);
});
});
@ -151,7 +87,7 @@ describe('KmnFileWriter', function () {
});
describe("writeData_Stores() ", function () {
describe("write_KmnFileHeader() ", function () {
const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions);
const sut_r = new KeylayoutFileReader(compilerTestCallbacks);
const sut_w = new KmnFileWriter(compilerTestCallbacks, compilerTestOptions);
@ -186,18 +122,18 @@ describe('KmnFileWriter', function () {
+ "group(main) using keys\n\n"
+ "\n";
it(('writeData_Stores should return store text with filename ').padEnd(62, " ") + 'on correct input', async function () {
const written_correctName = sut_w.writeData_Stores(converted);
it(('write_KmnFileHeader should return store text with filename ').padEnd(62, " ") + 'on correct input', async function () {
const written_correctName = sut_w.write_KmnFileHeader(converted);
assert.equal(written_correctName, (out_expected_first + converted.keylayout_filename + out_expected_last));
});
it(('writeData_Stores should return store text without filename ').padEnd(62, " ") + 'on empty input', async function () {
const written_emptyName = sut_w.writeData_Stores(converted_empty);
it(('write_KmnFileHeader should return store text without filename ').padEnd(62, " ") + 'on empty input', async function () {
const written_emptyName = sut_w.write_KmnFileHeader(converted_empty);
assert.equal(written_emptyName, (out_expected_first + out_expected_last));
});
it(('writeData_Stores should return store text without filename ').padEnd(62, " ") + 'on only filename as input', async function () {
const written_onlyName = sut_w.writeData_Stores(converted_unavailable);
it(('write_KmnFileHeader should return store text without filename ').padEnd(62, " ") + 'on only filename as input', async function () {
const written_onlyName = sut_w.write_KmnFileHeader(converted_unavailable);
assert.equal(written_onlyName, (out_expected_first + converted_unavailable.keylayout_filename + out_expected_last));
});
});
@ -502,7 +438,7 @@ describe('KmnFileWriter', function () {
});
});
describe('write form intermediate data array', function () {
describe('write from intermediate data array', function () {
const sut_w = new KmnFileWriter(compilerTestCallbacks, compilerTestOptions);
[
[