From cf4022ac8a533f882c8eba0e7c43e586bc8b3af4 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Mon, 24 Apr 2023 07:26:43 +0700 Subject: [PATCH] feat(developer): use CompilerEvent for messages in all of kmc Cleans up majority of remaining kmc-* modules to use CompilerEvent messages instead of console.log and friends, adding InfrastructureMessages class for kmc CLI messages. Consolidates schema loading functions into a single `loadSchema` function in the CompilerCallbacks interface and implementations. Moves responsibility for instantiating NodeCompilerCallbacks out of each individual BuildActivity and into higher level modules. Fixes return value for failing builds. --- common/web/types/src/main.ts | 2 +- .../web/types/src/util/compiler-interfaces.ts | 13 +++- common/web/types/test/helpers/index.ts | 18 +---- .../test/helpers/reader-callback-test.ts | 33 ++++---- .../src/common/web/test-helpers/index.ts | 33 ++++---- .../src/kmc-keyboard/src/compiler/compiler.ts | 4 +- .../src/kmc-keyboard/test/helpers/index.ts | 2 +- developer/src/kmc/src/commands/build.ts | 78 ++++++++++++------- .../kmc/src/commands/build/BuildActivity.ts | 3 +- .../src/commands/build/BuildKmnKeyboard.ts | 6 +- .../src/commands/build/BuildLdmlKeyboard.ts | 10 +-- .../src/kmc/src/commands/build/BuildModel.ts | 6 +- .../kmc/src/commands/build/BuildPackage.ts | 6 +- .../kmc/src/commands/build/BuildProject.ts | 18 ++--- .../kmc/src/commands/buildTestData/index.ts | 2 +- developer/src/kmc/src/kmlmc.ts | 2 +- developer/src/kmc/src/kmlmi.ts | 2 +- developer/src/kmc/src/kmlmp.ts | 2 +- .../NodeCompilerCallbacks.ts | 20 ++--- developer/src/kmc/src/messages/messages.ts | 39 ++++++++++ 20 files changed, 171 insertions(+), 128 deletions(-) rename developer/src/kmc/src/{util => messages}/NodeCompilerCallbacks.ts (55%) create mode 100644 developer/src/kmc/src/messages/messages.ts diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 31acbc74e7..3ebb5bd549 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -18,7 +18,7 @@ export { default as LDMLKeyboardXMLSourceFileReader } from './ldml-keyboard/ldml export * as Constants from './consts/virtual-key-constants.js'; -export { CompilerCallbacks, CompilerEvent, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec, compilerErrorSeverityName } from './util/compiler-interfaces.js'; +export { CompilerCallbacks, CompilerSchema, CompilerEvent, CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec, compilerErrorSeverityName } from './util/compiler-interfaces.js'; export { CommonTypesMessages } from './util/common-events.js'; export * as TouchLayout from './keyman-touch-layout/keyman-touch-layout-file.js'; diff --git a/common/web/types/src/util/compiler-interfaces.ts b/common/web/types/src/util/compiler-interfaces.ts index 2fe668417f..0e7e63759b 100644 --- a/common/web/types/src/util/compiler-interfaces.ts +++ b/common/web/types/src/util/compiler-interfaces.ts @@ -66,6 +66,13 @@ export enum CompilerErrorNamespace { Infrastructure = 0x5000, }; +export type CompilerSchema = + 'ldml-keyboard' | + 'ldml-keyboardtest' | + 'kvks' | + 'kpj'; + // | 'keyman-touch-layout.clean'; TODO this has the wrong name pattern, .spec.json instead of .schema.json + /** * Abstract interface for callbacks, to abstract out file i/o */ @@ -76,11 +83,9 @@ export interface CompilerCallbacks { * @param filename */ loadFile(baseFilename: string, filename: string | URL): Buffer; - loadLdmlKeyboardSchema(): Buffer; - loadLdmlKeyboardTestSchema(): Buffer; + loadSchema(schema: CompilerSchema): Buffer; reportMessage(event: CompilerEvent): void; - loadKvksJsonSchema(): Buffer; - loadKpjJsonSchema(): Buffer; + debug(msg: string): void; }; /** diff --git a/common/web/types/test/helpers/index.ts b/common/web/types/test/helpers/index.ts index a6848021ef..984b21c7bf 100644 --- a/common/web/types/test/helpers/index.ts +++ b/common/web/types/test/helpers/index.ts @@ -1,6 +1,7 @@ import path from "path"; import fs from "fs"; import { fileURLToPath } from "url"; +import { CompilerSchema } from "../../src/util/compiler-interfaces.js"; /** * Builds a path to the fixture with the given path components. @@ -13,11 +14,8 @@ export function makePathToFixture(...components: string[]): string { return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url)); } -export function loadKvksJsonSchema(): Buffer { - return fs.readFileSync(new URL(path.join('..', '..', 'src', 'kvks.schema.json'), import.meta.url)); -} - export function loadKeymanTouchLayoutCleanJsonSchema(): Buffer { + // TODO: this has the Wrong Name Pattern! return fs.readFileSync(new URL(path.join('..', '..', 'src', 'keyman-touch-layout.clean.spec.json'), import.meta.url)); } @@ -26,14 +24,6 @@ export function loadFile(baseFilename: string, filename: string | URL): Buffer { return fs.readFileSync(filename); } -export function loadLdmlKeyboardSchema(): Buffer { - return fs.readFileSync(new URL(path.join('..', '..', 'src', 'ldml-keyboard.schema.json'), import.meta.url)); -} - -export function loadLdmlKeyboardTestDataSchema(): Buffer { - return fs.readFileSync(new URL(path.join('..', '..', 'src', 'ldml-keyboardtest.schema.json'), import.meta.url)); -} - -export function loadKpjJsonSchema(): Buffer { - return fs.readFileSync(new URL(path.join('..', '..', 'src', 'kpj.schema.json'), import.meta.url)); +export function loadSchema(schema: CompilerSchema): Buffer { + return fs.readFileSync(new URL(path.join('..', '..', 'src', schema + '.schema.json'), import.meta.url)); } diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/common/web/types/test/helpers/reader-callback-test.ts index 983db078f6..847383461b 100644 --- a/common/web/types/test/helpers/reader-callback-test.ts +++ b/common/web/types/test/helpers/reader-callback-test.ts @@ -1,8 +1,8 @@ import 'mocha'; import {assert} from 'chai'; -import {loadLdmlKeyboardSchema, loadFile, makePathToFixture, loadLdmlKeyboardTestDataSchema} from '../helpers/index.js'; +import { loadFile, makePathToFixture, loadSchema } from '../helpers/index.js'; import LDMLKeyboardXMLSourceFileReader from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; -import { CompilerCallbacks, CompilerEvent } from '../../src/util/compiler-interfaces.js'; +import { CompilerCallbacks, CompilerEvent, CompilerSchema } from '../../src/util/compiler-interfaces.js'; import { LDMLKeyboardXMLSourceFile } from '../../src/ldml-keyboard/ldml-keyboard-xml.js'; import { LDMLKeyboardTestDataXMLSourceFile } from '../ldml-keyboard/ldml-keyboard-testdata-xml.js'; @@ -13,21 +13,24 @@ import { LDMLKeyboardTestDataXMLSourceFile } from '../ldml-keyboard/ldml-keyboar * A CompilerCallbacks implementation for testing */ class TestCompilerCallbacks implements CompilerCallbacks { - loadKpjJsonSchema(): Buffer { - throw new Error('loadKpjJsonSchema not implemented.'); // not needed for this test - } - loadLdmlKeyboardTestSchema(): Buffer { - return loadLdmlKeyboardTestDataSchema(); - } - loadLdmlKeyboardSchema(): Buffer { - return loadLdmlKeyboardSchema(); - } - loadKvksJsonSchema(): Buffer { - throw new Error('loadKvksJsonSchema not implemented.'); + loadSchema(schema: CompilerSchema): Buffer { + switch(schema) { + case 'kpj': + throw new Error('loadKpjJsonSchema not implemented.'); // not needed for this test + case 'kvks': + throw new Error('loadKvksJsonSchema not implemented.'); + case 'ldml-keyboard': + return loadSchema(schema); + case 'ldml-keyboardtest': + return loadSchema(schema); + } } clear() { this.messages = []; } + debug(msg: string): void { + console.debug(msg); + } messages: CompilerEvent[] = []; loadFile(baseFilename: string, filename: string | URL): Buffer { try { @@ -128,9 +131,9 @@ export function testReaderCases(cases : CompilationCase[]) { } // special case for an expected exception if (testcase.throws) { - assert.throws(() => reader.validate(source, loadLdmlKeyboardSchema()), testcase.throws); + assert.throws(() => reader.validate(source, loadSchema('ldml-keyboard')), testcase.throws); } else { - assert.doesNotThrow(() => reader.validate(source, loadLdmlKeyboardSchema()), `validating ${testcase.subpath}`); + assert.doesNotThrow(() => reader.validate(source, loadSchema('ldml-keyboard')), `validating ${testcase.subpath}`); // if we expected errors or warnings, show them if (testcase.errors) { assert.includeDeepMembers(callbacks.messages, testcase.errors, 'expected errors to be included'); diff --git a/developer/src/common/web/test-helpers/index.ts b/developer/src/common/web/test-helpers/index.ts index a68c8dc0af..52835d5e26 100644 --- a/developer/src/common/web/test-helpers/index.ts +++ b/developer/src/common/web/test-helpers/index.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import { CompilerEvent, CompilerCallbacks } from '@keymanapp/common-types'; +import { CompilerEvent, CompilerCallbacks, CompilerSchema } from '@keymanapp/common-types'; // TODO: schemas are only used by kmc-keyboard for now, so this works at this // time, but it's a little fragile if we need them elsewhere in the future @@ -9,10 +9,20 @@ const SCHEMA_BASE = '../../../../kmc-keyboard/build/src/'; * A CompilerCallbacks implementation for testing */ export class TestCompilerCallbacks implements CompilerCallbacks { + /* TestCompilerCallbacks */ + + messages: CompilerEvent[] = []; + clear() { this.messages = []; } - messages: CompilerEvent[] = []; + + hasMessage(code: number): boolean { + return this.messages.find((item) => item.code == code) === undefined ? false : true; + } + + /* CompilerCallbacks */ + loadFile(baseFilename: string, filename: string | URL): Buffer { // TODO: translate filename based on the baseFilename try { @@ -25,23 +35,16 @@ export class TestCompilerCallbacks implements CompilerCallbacks { } } } - hasMessage(code: number): boolean { - return this.messages.find((item) => item.code == code) === undefined ? false : true; - } reportMessage(event: CompilerEvent): void { // console.log(event.message); this.messages.push(event); } - loadLdmlKeyboardSchema(): Buffer { - return fs.readFileSync(new URL(SCHEMA_BASE + 'ldml-keyboard.schema.json', import.meta.url)); + + loadSchema(schema: CompilerSchema): Buffer { + return fs.readFileSync(new URL(SCHEMA_BASE + schema + '.schema.json', import.meta.url)); } - loadKvksJsonSchema(): Buffer { - return fs.readFileSync(new URL(SCHEMA_BASE + 'kvks.schema.json', import.meta.url)); - } - loadKpjJsonSchema(): Buffer { - return fs.readFileSync(new URL(SCHEMA_BASE + 'kpj.schema.json', import.meta.url)); - } - loadLdmlKeyboardTestSchema(): Buffer { - return fs.readFileSync(new URL(SCHEMA_BASE + 'ldml-keyboardtest.schema.json', import.meta.url)); + + debug(msg: string) { + console.debug(msg); } }; \ No newline at end of file diff --git a/developer/src/kmc-keyboard/src/compiler/compiler.ts b/developer/src/kmc-keyboard/src/compiler/compiler.ts index 7d6a8142b4..8f7de23eba 100644 --- a/developer/src/kmc-keyboard/src/compiler/compiler.ts +++ b/developer/src/kmc-keyboard/src/compiler/compiler.ts @@ -66,7 +66,7 @@ export default class Compiler { return null; } try { - if (!reader.validate(source, this.callbacks.loadLdmlKeyboardSchema())) { + if (!reader.validate(source, this.callbacks.loadSchema('ldml-keyboard'))) { return null; } } catch(e) { @@ -98,7 +98,7 @@ export default class Compiler { // TODO-LDML: The unboxed data doesn't match the schema anymore. Skipping validation, for now. // try { - // if (!reader.validate(source, this.callbacks.loadLdmlKeyboardTestSchema())) { + // if (!reader.validate(source, this.callbacks.loadSchema('ldml-keyboard-test'))) { // return null; // } // } catch(e) { diff --git a/developer/src/kmc-keyboard/test/helpers/index.ts b/developer/src/kmc-keyboard/test/helpers/index.ts index 1fd2a5e411..391e105348 100644 --- a/developer/src/kmc-keyboard/test/helpers/index.ts +++ b/developer/src/kmc-keyboard/test/helpers/index.ts @@ -54,7 +54,7 @@ export function loadSectionFixture(compilerClass: typeof SectionCompiler, filena const source = reader.load(data); assert.isNotNull(source); - if (!reader.validate(source, callbacks.loadLdmlKeyboardSchema())) { + if (!reader.validate(source, callbacks.loadSchema('ldml-keyboard'))) { return null; // mimic kmc behavior - bail if validate fails } diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index cbaf13a0de..fda5ae84fa 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -3,6 +3,8 @@ import { Command } from 'commander'; import { BuildActivityOptions } from './build/BuildActivity.js'; import { buildActivities } from './build/buildActivities.js'; import { BuildProject } from './build/BuildProject.js'; +import { NodeCompilerCallbacks } from 'src/messages/NodeCompilerCallbacks.js'; +import { InfrastructureMessages } from 'src/messages/messages.js'; export function declareBuild(program: Command) { program @@ -13,43 +15,59 @@ export function declareBuild(program: Command) { .option('--no-compiler-version', 'Exclude compiler version metadata from output') .option('-w, --compiler-warnings-as-errors', 'Causes warnings to fail the build') .option('--no-warn-deprecated-code', 'Turn off warnings for deprecated code styles') - .action((infiles: string[], options: any) => { - let p = []; - if(!infiles.length) { - console.debug('Assuming infile == .'); - p.push(build('.', options)); + .action(async (filenames: string[], options: any) => { + if(!filenames.length) { + // If there are no filenames provided, then we are building the current + // folder ('.') as a project-style build + filenames.push('.'); } - for(let infile of infiles) { - p.push(build(infile, options)); + + for(let filename of filenames) { + if(!await build(filename, options)) { + // Once a file fails to build, we bail on subsequent builds + // TODO: is this the most appropriate semantics? + process.exit(1); + } } - return Promise.all(p).then(); }); } -async function build(infile: string, options: BuildActivityOptions): Promise { - console.log(`Building ${infile}`); +async function build(filename: string, options: BuildActivityOptions): Promise { + let callbacks = new NodeCompilerCallbacks(); - if(!fs.existsSync(infile)) { - // TODO: consolidate errors - console.error(`File ${infile} does not exist`); - process.exit(2); - } + try { + callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename})); - // If infile is a directory, then we treat that as a project and build it - if(fs.statSync(infile).isDirectory()) { - return (new BuildProject()).build(infile, options); - } - - // Otherwise, if it's one of our known file extensions, we build it - let extensions: string[] = []; - for(let build of buildActivities) { - if(infile.toLowerCase().endsWith(build.sourceExtension)) { - return build.build(infile, options); + if(!fs.existsSync(filename)) { + callbacks.reportMessage(InfrastructureMessages.Error_FileDoesNotExist({filename})); + return false; } - extensions.push(build.sourceExtension); - } - // TODO: consolidate errors - console.error(`Unrecognised input file ${infile}, expecting ${extensions.join(', ')}, or project folder`); - process.exit(2); + let builder = null; + + // If infile is a directory, then we treat that as a project and build it + if(fs.statSync(filename).isDirectory()) { + builder = new BuildProject(); + } else { + // Otherwise, if it's one of our known file extensions, we build it + let extensions: string[] = []; + builder = buildActivities.find(build => { extensions.push(build.sourceExtension); return filename.toLowerCase().endsWith(build.sourceExtension) }); + if(!builder) { + callbacks.reportMessage(InfrastructureMessages.Error_FileTypeNotRecognized({filename, extensions: extensions.join(', ')})); + return false; + } + } + + let result = await builder.build(filename, callbacks, options); + if(result) { + callbacks.reportMessage(InfrastructureMessages.Info_FileBuiltSuccessfully({filename})); + } else { + callbacks.reportMessage(InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename})); + } + + return result; + } catch(e) { + callbacks.reportMessage(InfrastructureMessages.Fatal_UnexpectedException({e})); + return false; + } } diff --git a/developer/src/kmc/src/commands/build/BuildActivity.ts b/developer/src/kmc/src/commands/build/BuildActivity.ts index 6de4c4785e..83f5aa4aba 100644 --- a/developer/src/kmc/src/commands/build/BuildActivity.ts +++ b/developer/src/kmc/src/commands/build/BuildActivity.ts @@ -1,3 +1,4 @@ +import { CompilerCallbacks } from "@keymanapp/common-types"; import { escapeRegExp } from "../../util/escapeRegExp.js"; export interface BuildActivityOptions { @@ -13,7 +14,7 @@ export abstract class BuildActivity { public abstract get sourceExtension(): string; public abstract get compiledExtension(): string; public abstract get description(): string; - public abstract build(infile: string, options: BuildActivityOptions): Promise; + public abstract build(infile: string, callbacks: CompilerCallbacks, options: BuildActivityOptions): Promise; protected getOutputFilename(infile: string, options: BuildActivityOptions): string { return options.outFile ? options.outFile : diff --git a/developer/src/kmc/src/commands/build/BuildKmnKeyboard.ts b/developer/src/kmc/src/commands/build/BuildKmnKeyboard.ts index eda098c157..cd5b7c7ac3 100644 --- a/developer/src/kmc/src/commands/build/BuildKmnKeyboard.ts +++ b/developer/src/kmc/src/commands/build/BuildKmnKeyboard.ts @@ -2,21 +2,19 @@ import * as path from 'path'; import { BuildActivity, BuildActivityOptions } from './BuildActivity.js'; import { Compiler } from '@keymanapp/kmc-kmn'; import { platform } from 'os'; -import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js'; +import { CompilerCallbacks } from '@keymanapp/common-types'; export class BuildKmnKeyboard extends BuildActivity { public get name(): string { return 'Keyman keyboard'; } public get sourceExtension(): string { return '.kmn'; } public get compiledExtension(): string { return '.kmx'; } public get description(): string { return 'Build a Keyman keyboard'; } - public async build(infile: string, options: BuildActivityOptions): Promise { + public async build(infile: string, callbacks: CompilerCallbacks, options: BuildActivityOptions): Promise { let compiler = new Compiler(); if(!await compiler.init()) { return false; } - const callbacks = new NodeCompilerCallbacks(); - // We need to resolve paths to absolute paths before calling kmc-kmn let outfile = this.getOutputFilename(infile, options); diff --git a/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts b/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts index 39fe152b3c..867b5e49bc 100644 --- a/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts +++ b/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts @@ -2,7 +2,6 @@ import * as path from 'path'; import * as fs from 'fs'; import * as kmc from '@keymanapp/kmc-keyboard'; import { KvkFileWriter, CompilerCallbacks } from '@keymanapp/common-types'; -import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js'; import { BuildActivity, BuildActivityOptions } from './BuildActivity.js'; export class BuildLdmlKeyboard extends BuildActivity { @@ -10,10 +9,10 @@ export class BuildLdmlKeyboard extends BuildActivity { public get sourceExtension(): string { return '.xml'; } public get compiledExtension(): string { return '.kmx'; } public get description(): string { return 'Build a LDML keyboard'; } - public async build(infile: string, options: BuildActivityOptions): Promise { + public async build(infile: string, callbacks: CompilerCallbacks, options: BuildActivityOptions): Promise { // TODO-LDML: consider hardware vs touch -- touch-only layout will not have a .kvk // Compile: - let [kmx,kvk,kmw] = buildLdmlKeyboardToMemory(infile, options); + let [kmx,kvk,kmw] = buildLdmlKeyboardToMemory(infile, callbacks, options); // Output: const fileBaseName = options.outFile ?? infile; @@ -43,7 +42,7 @@ export class BuildLdmlKeyboard extends BuildActivity { } } -function buildLdmlKeyboardToMemory(inputFilename: string, options: BuildActivityOptions): [Uint8Array, Uint8Array, Uint8Array] { +function buildLdmlKeyboardToMemory(inputFilename: string, callbacks: CompilerCallbacks, options: BuildActivityOptions): [Uint8Array, Uint8Array, Uint8Array] { let compilerOptions: kmc.CompilerOptions = { debug: options.debug ?? false, addCompilerVersion: options.compilerVersion ?? true, @@ -51,8 +50,7 @@ function buildLdmlKeyboardToMemory(inputFilename: string, options: BuildActivity // TODO: treatWarningsAsErrors: options.treatWarningsAsErrors, } - const c: CompilerCallbacks = new NodeCompilerCallbacks(); - const k = new kmc.Compiler(c, options); + const k = new kmc.Compiler(callbacks, options); let source = k.load(inputFilename); if (!source) { return [null, null, null]; diff --git a/developer/src/kmc/src/commands/build/BuildModel.ts b/developer/src/kmc/src/commands/build/BuildModel.ts index f35fb3f892..c6819aa07b 100644 --- a/developer/src/kmc/src/commands/build/BuildModel.ts +++ b/developer/src/kmc/src/commands/build/BuildModel.ts @@ -1,19 +1,17 @@ import * as fs from 'fs'; import { BuildActivity, BuildActivityOptions } from './BuildActivity.js'; import { compileModel } from '@keymanapp/kmc-model'; -import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js'; +import { CompilerCallbacks } from '@keymanapp/common-types'; export class BuildModel extends BuildActivity { public get name(): string { return 'Lexical model'; } public get sourceExtension(): string { return '.model.ts'; } public get compiledExtension(): string { return '.model.js'; } public get description(): string { return 'Build a lexical model'; } - public async build(infile: string, options: BuildActivityOptions): Promise { + public async build(infile: string, callbacks: CompilerCallbacks, options: BuildActivityOptions): Promise { let outputFilename: string = this.getOutputFilename(infile, options); let code = null; - const callbacks = new NodeCompilerCallbacks(); - // Compile: try { code = compileModel(infile, callbacks); diff --git a/developer/src/kmc/src/commands/build/BuildPackage.ts b/developer/src/kmc/src/commands/build/BuildPackage.ts index 5351210a84..742ae8fd76 100644 --- a/developer/src/kmc/src/commands/build/BuildPackage.ts +++ b/developer/src/kmc/src/commands/build/BuildPackage.ts @@ -2,15 +2,13 @@ import * as fs from 'fs'; import { BuildActivity, BuildActivityOptions } from './BuildActivity.js'; import KmpCompiler from '@keymanapp/kmc-package'; import { CompilerCallbacks } from '@keymanapp/common-types'; -import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js'; export class BuildPackage extends BuildActivity { public get name(): string { return 'Package'; } public get sourceExtension(): string { return '.kps'; } public get compiledExtension(): string { return '.kmp'; } public get description(): string { return 'Build a Keyman package'; } - public async build(infile: string, options: BuildActivityOptions): Promise { - const c: CompilerCallbacks = new NodeCompilerCallbacks(); + public async build(infile: string, callbacks: CompilerCallbacks, options: BuildActivityOptions): Promise { const outfile = this.getOutputFilename(infile, options); @@ -19,7 +17,7 @@ export class BuildPackage extends BuildActivity { // const kpsString: string = fs.readFileSync(infile, 'utf8'); - const kmpCompiler = new KmpCompiler(c); + const kmpCompiler = new KmpCompiler(callbacks); const kmpJsonData = kmpCompiler.transformKpsToKmpObject(kpsString, infile); if(!kmpJsonData) { return false; diff --git a/developer/src/kmc/src/commands/build/BuildProject.ts b/developer/src/kmc/src/commands/build/BuildProject.ts index f3470e11b6..63f100f0b2 100644 --- a/developer/src/kmc/src/commands/build/BuildProject.ts +++ b/developer/src/kmc/src/commands/build/BuildProject.ts @@ -1,10 +1,10 @@ import * as path from 'path'; import * as fs from 'fs'; import { CompilerCallbacks, KeymanDeveloperProject, KPJFileReader } from '@keymanapp/common-types'; -import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js'; import { KeymanDeveloperProjectFile } from '../../../../../../common/web/types/src/kpj/keyman-developer-project.js'; import { BuildActivity, BuildActivityOptions } from './BuildActivity.js'; import { buildActivities } from './buildActivities.js'; +import { InfrastructureMessages } from 'src/messages/messages.js'; const PROJECT_EXTENSION = '.kpj'; @@ -13,27 +13,27 @@ export class BuildProject extends BuildActivity { public get sourceExtension(): string { return PROJECT_EXTENSION; } public get compiledExtension(): string { return null; } public get description(): string { return 'Build a keyboard or lexical model project'; } - public async build(infile: string, options: BuildActivityOptions): Promise { - let builder = new ProjectBuilder(infile, options); + public async build(infile: string, callbacks: CompilerCallbacks, options: BuildActivityOptions): Promise { + let builder = new ProjectBuilder(infile, callbacks, options); return builder.run(); } } class ProjectBuilder { - callbacks: CompilerCallbacks = new NodeCompilerCallbacks(); + callbacks: CompilerCallbacks; infile: string; options: BuildActivityOptions; project: KeymanDeveloperProject; - constructor(infile: string, options: BuildActivityOptions) { + constructor(infile: string, callbacks: CompilerCallbacks, options: BuildActivityOptions) { this.infile = path.resolve(infile); + this.callbacks = callbacks; this.options = options; } async run(): Promise { if(this.options.outFile) { - // TODO: callbacks.reportMessage - console.error('--out-file should not be specified for project builds'); + this.callbacks.reportMessage(InfrastructureMessages.Error_OutFileNotValidForProjects()); return false; } @@ -90,7 +90,7 @@ class ProjectBuilder { const kpjData = this.callbacks.loadFile(null, this.infile); const reader = new KPJFileReader(); const kpj = reader.read(kpjData); - const schema = this.callbacks.loadKpjJsonSchema(); + const schema = this.callbacks.loadSchema('kpj'); try { reader.validate(kpj, schema); } catch(e) { @@ -121,7 +121,7 @@ class ProjectBuilder { fs.mkdirSync(path.dirname(options.outFile), {recursive:true}); - let result = await activity.build(infile, options); + let result = await activity.build(infile, this.callbacks, options); if(result) { console.log(`${path.basename(infile )} built successfully.`); } else { diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts index 326b4108df..2341b11082 100644 --- a/developer/src/kmc/src/commands/buildTestData/index.ts +++ b/developer/src/kmc/src/commands/buildTestData/index.ts @@ -2,7 +2,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as kmc from '@keymanapp/kmc-keyboard'; import { CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; -import { NodeCompilerCallbacks } from '../../util/NodeCompilerCallbacks.js'; +import { NodeCompilerCallbacks } from '../../messages/NodeCompilerCallbacks.js'; export interface BuildTestDataOptions { outFile?: string; diff --git a/developer/src/kmc/src/kmlmc.ts b/developer/src/kmc/src/kmlmc.ts index cda20d9562..ad607fcb47 100644 --- a/developer/src/kmc/src/kmlmc.ts +++ b/developer/src/kmc/src/kmlmc.ts @@ -8,7 +8,7 @@ import { Command } from 'commander'; import { compileModel } from '@keymanapp/kmc-model'; import { SysExits } from './util/sysexits.js'; import KEYMAN_VERSION from "@keymanapp/keyman-version"; -import { NodeCompilerCallbacks } from './util/NodeCompilerCallbacks.js'; +import { NodeCompilerCallbacks } from './messages/NodeCompilerCallbacks.js'; let inputFilename: string; const program = new Command(); diff --git a/developer/src/kmc/src/kmlmi.ts b/developer/src/kmc/src/kmlmi.ts index 2f3963f469..39a9eee36c 100644 --- a/developer/src/kmc/src/kmlmi.ts +++ b/developer/src/kmc/src/kmlmi.ts @@ -10,7 +10,7 @@ import KmpCompiler from '@keymanapp/kmc-package'; import { ModelInfoOptions as ModelInfoOptions, writeMergedModelMetadataFile } from '@keymanapp/kmc-model-info'; import { SysExits } from './util/sysexits.js'; import KEYMAN_VERSION from "@keymanapp/keyman-version"; -import { NodeCompilerCallbacks } from './util/NodeCompilerCallbacks.js'; +import { NodeCompilerCallbacks } from './messages/NodeCompilerCallbacks.js'; let inputFilename: string; const program = new Command(); diff --git a/developer/src/kmc/src/kmlmp.ts b/developer/src/kmc/src/kmlmp.ts index 6e66d00bbc..ca96373f1b 100644 --- a/developer/src/kmc/src/kmlmp.ts +++ b/developer/src/kmc/src/kmlmp.ts @@ -8,7 +8,7 @@ import { Command } from 'commander'; import KmpCompiler from '@keymanapp/kmc-package'; import { SysExits } from './util/sysexits.js'; import KEYMAN_VERSION from "@keymanapp/keyman-version"; -import { NodeCompilerCallbacks } from './util/NodeCompilerCallbacks.js'; +import { NodeCompilerCallbacks } from './messages/NodeCompilerCallbacks.js'; let inputFilename: string; const program = new Command(); diff --git a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts b/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts similarity index 55% rename from developer/src/kmc/src/util/NodeCompilerCallbacks.ts rename to developer/src/kmc/src/messages/NodeCompilerCallbacks.ts index 7c9ccb80e8..ea9ddaee0b 100644 --- a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts @@ -1,5 +1,5 @@ import * as fs from 'fs'; -import { CompilerCallbacks, CompilerEvent, compilerErrorSeverityName } from '@keymanapp/common-types'; +import { CompilerCallbacks, CompilerSchema, CompilerEvent, compilerErrorSeverityName } from '@keymanapp/common-types'; /** * Concrete implementation for CLI use @@ -27,20 +27,12 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { } } - loadLdmlKeyboardSchema(): Buffer { - let schemaPath = new URL('ldml-keyboard.schema.json', import.meta.url); - return fs.readFileSync(schemaPath); + debug(msg: string) { + console.debug(msg); } - loadLdmlKeyboardTestSchema(): Buffer { - let schemaPath = new URL('ldml-keyboardtest.schema.json', import.meta.url); - return fs.readFileSync(schemaPath); - } - loadKvksJsonSchema(): Buffer { - let schemaPath = new URL('kvks.schema.json', import.meta.url); - return fs.readFileSync(schemaPath); - } - loadKpjJsonSchema(): Buffer { - let schemaPath = new URL('kpj.schema.json', import.meta.url); + + loadSchema(schema: CompilerSchema) { + let schemaPath = new URL(schema + '.schema.json', import.meta.url); return fs.readFileSync(schemaPath); } } diff --git a/developer/src/kmc/src/messages/messages.ts b/developer/src/kmc/src/messages/messages.ts new file mode 100644 index 0000000000..2b2f779e99 --- /dev/null +++ b/developer/src/kmc/src/messages/messages.ts @@ -0,0 +1,39 @@ +import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m } from "@keymanapp/common-types"; + +const Namespace = CompilerErrorNamespace.Infrastructure; +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; + +export class InfrastructureMessages { + static Fatal_UnexpectedException = (o:{e: any}) => m(this.FATAL_UnexpectedException, + `Unexpected exception: ${(o.e ?? 'unknown error').toString()}\n\nCall stack:\n${(o.e instanceof Error ? o.e.stack : (new Error()).stack)}`); + static FATAL_UnexpectedException = SevFatal | 0x0001; + + static Info_BuildingFile = (o:{filename:string}) => m(this.INFO_BuildingFile, + `Building ${o.filename}`); + static INFO_BuildingFile = SevInfo | 0x0002; + + static Error_FileDoesNotExist = (o:{filename:string}) => m(this.ERROR_FileDoesNotExist, + `File ${o.filename} does not exist`); + static ERROR_FileDoesNotExist = SevError | 0x0003; + + static Error_FileTypeNotRecognized = (o:{filename: string, extensions: string}) => m(this.ERROR_FileTypeNotRecognized, + `Unrecognised input file ${o.filename}, expecting ${o.extensions}, or project folder`); + static ERROR_FileTypeNotRecognized = SevError | 0x0004; + + static Error_OutFileNotValidForProjects = () => m(this.ERROR_OutFileNotValidForProjects, + `--out-file should not be specified for project builds`); + static ERROR_OutFileNotValidForProjects = SevError | 0x0005; + + static Info_FileBuiltSuccessfully = (o:{filename:string}) => m(this.INFO_FileBuiltSuccessfully, + `${o.filename} built successfully.`); + static INFO_FileBuiltSuccessfully = SevInfo | 0x0006; + + static Info_FileNotBuiltSuccessfully = (o:{filename:string}) => m(this.INFO_FileNotBuiltSuccessfully, + `${o.filename} failed to build.`); + static INFO_FileNotBuiltSuccessfully = SevInfo | 0x0007; +} +