From d2f4e5f2c73efbd1e77b857cdd0750cdd1128726 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 1 Sep 2023 18:00:13 +0400 Subject: [PATCH] feat(developer): add --log-format to kmc Adds logFormat of tsv vs formatted for kmc, in order to allow for "porcelain" style log formatting when we interop with TIKE. --- common/web/types/src/main.ts | 4 ++- .../web/types/src/util/compiler-interfaces.ts | 25 ++++++++++++++++++- developer/src/kmc/src/commands/build.ts | 13 +++++----- .../src/commands/buildClasses/BuildProject.ts | 6 ++--- .../src/messages/infrastructureMessages.ts | 20 +++++++-------- .../src/kmc/src/util/NodeCompilerCallbacks.ts | 23 ++++++++++++++++- developer/src/kmc/src/util/baseOptions.ts | 7 +++++- 7 files changed, 75 insertions(+), 23 deletions(-) diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 430b88cdc3..a814903ab7 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -25,7 +25,9 @@ export { defaultCompilerOptions, CompilerBaseOptions, CompilerCallbacks, Compile CompilerErrorSeverity, CompilerPathCallbacks, CompilerFileSystemCallbacks, CompilerCallbackOptions, CompilerError, CompilerMessageSpec, compilerErrorSeverity, CompilerErrorMask, CompilerFileCallbacks, compilerErrorSeverityName, compilerExceptionToString, compilerErrorFormatCode, - compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS } from './util/compiler-interfaces.js'; + compilerLogLevelToSeverity, CompilerLogLevel, compilerEventFormat, ALL_COMPILER_LOG_LEVELS, + ALL_COMPILER_LOG_FORMATS, CompilerLogFormat, + } 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 3283fb2598..b27a170ab3 100644 --- a/common/web/types/src/util/compiler-interfaces.ts +++ b/common/web/types/src/util/compiler-interfaces.ts @@ -74,11 +74,20 @@ export class CompilerError { * @param filename * @returns */ - static formatFilename(filename: string): string { + static formatFilename(filename: string, options?: { + fullPath?: boolean, + forwardSlashes?: boolean + }): string { if(!filename) { return ''; } + if(options?.fullPath) { + return options?.forwardSlashes ? + filename.replaceAll(/\\/g, '/') : + filename.replaceAll(/\//g, '\\'); + } + let x = filename.lastIndexOf('/'); if(x < 0) { x = filename.lastIndexOf('\\'); @@ -239,6 +248,7 @@ export interface CompilerFileSystemCallbacks { export interface CompilerCallbackOptions { logLevel?: CompilerLogLevel; + logFormat?: CompilerLogFormat; color?: boolean; // null or undefined == use console default compilerWarningsAsErrors?: boolean; }; @@ -355,6 +365,10 @@ export interface CompilerBaseOptions { * all messages are still reported to the internal log) */ logLevel?: CompilerLogLevel; + /** + * Format of output for log to console + */ + logFormat?: CompilerLogFormat; /** * Optional output file for activities that generate output */ @@ -390,6 +404,7 @@ export interface CompilerOptions extends CompilerBaseOptions { export const defaultCompilerOptions: CompilerOptions = { logLevel: 'info', + logFormat: 'formatted', // outFile: (undefined) saveDebug: false, shouldAddCompilerVersion: true, @@ -443,3 +458,11 @@ export const compilerLogLevelToSeverity: {[index in CompilerLogLevel]: number} = 'info': CompilerErrorSeverity.Info, 'debug': CompilerErrorSeverity.Info }; + +export const ALL_COMPILER_LOG_FORMATS = [ + 'tsv', + 'formatted' +] as const; + +type CompilerLogFormatTuple = typeof ALL_COMPILER_LOG_FORMATS; +export type CompilerLogFormat = CompilerLogFormatTuple[number]; diff --git a/developer/src/kmc/src/commands/build.ts b/developer/src/kmc/src/commands/build.ts index 9568da7020..931833c714 100644 --- a/developer/src/kmc/src/commands/build.ts +++ b/developer/src/kmc/src/commands/build.ts @@ -19,6 +19,7 @@ function commandOptionsToCompilerOptions(options: any): CompilerOptions { // CompilerBaseOptions outFile: options.outFile, logLevel: options.logLevel, + logFormat: options.logFormat, color: options.color, // CompilerOptions shouldAddCompilerVersion: options.compilerVersion, @@ -112,22 +113,22 @@ async function build(filename: string, parentCallbacks: NodeCompilerCallbacks, o if(fs.statSync(filename).isDirectory()) { buildFilename = path.join(buildFilename, path.basename(buildFilename) + KeymanFileTypes.Source.Project); } - buildFilename = path.relative(process.cwd(), buildFilename).replace(/\\/g, '/'); + const relativeFilename = path.relative(process.cwd(), buildFilename).replace(/\\/g, '/'); const callbacks = new CompilerFileCallbacks(buildFilename, options, parentCallbacks); - callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename:buildFilename})); + callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename:buildFilename, relativeFilename})); let result = await builder.build(filename, callbacks, options); result = result && !callbacks.hasFailureMessage(); if(result) { callbacks.reportMessage(builder instanceof BuildProject - ? InfrastructureMessages.Info_ProjectBuiltSuccessfully({filename:buildFilename}) - : InfrastructureMessages.Info_FileBuiltSuccessfully({filename:buildFilename}) + ? InfrastructureMessages.Info_ProjectBuiltSuccessfully({filename:buildFilename, relativeFilename}) + : InfrastructureMessages.Info_FileBuiltSuccessfully({filename:buildFilename, relativeFilename}) ); } else { callbacks.reportMessage(builder instanceof BuildProject - ? InfrastructureMessages.Info_ProjectNotBuiltSuccessfully({filename:buildFilename}) - : InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename:buildFilename}) + ? InfrastructureMessages.Info_ProjectNotBuiltSuccessfully({filename:buildFilename, relativeFilename}) + : InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename:buildFilename, relativeFilename}) ); } diff --git a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts index 380be1b724..db74ccec06 100644 --- a/developer/src/kmc/src/commands/buildClasses/BuildProject.ts +++ b/developer/src/kmc/src/commands/buildClasses/BuildProject.ts @@ -87,7 +87,7 @@ class ProjectBuilder { const buildFilename = path.relative(process.cwd(), infile).replace(/\\/g, '/'); const callbacks = new CompilerFileCallbacks(buildFilename, options, this.callbacks); - callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename: buildFilename})); + callbacks.reportMessage(InfrastructureMessages.Info_BuildingFile({filename: infile, relativeFilename:buildFilename})); fs.mkdirSync(path.dirname(options.outFile), {recursive:true}); @@ -98,9 +98,9 @@ class ProjectBuilder { result = result && !callbacks.hasFailureMessage(this.options.compilerWarningsAsErrors ?? this.project.options.compilerWarningsAsErrors); if(result) { - callbacks.reportMessage(InfrastructureMessages.Info_FileBuiltSuccessfully({filename: buildFilename})); + callbacks.reportMessage(InfrastructureMessages.Info_FileBuiltSuccessfully({filename: infile, relativeFilename:buildFilename})); } else { - callbacks.reportMessage(InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename: buildFilename})); + callbacks.reportMessage(InfrastructureMessages.Info_FileNotBuiltSuccessfully({filename: infile, relativeFilename: buildFilename})); } return result; diff --git a/developer/src/kmc/src/messages/infrastructureMessages.ts b/developer/src/kmc/src/messages/infrastructureMessages.ts index ee828dae75..49b85404dd 100644 --- a/developer/src/kmc/src/messages/infrastructureMessages.ts +++ b/developer/src/kmc/src/messages/infrastructureMessages.ts @@ -12,8 +12,8 @@ export class InfrastructureMessages { static FATAL_UnexpectedException = SevFatal | 0x0001; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_BuildingFile = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_BuildingFile, - `Building ${o.filename}`)}); + static Info_BuildingFile = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_BuildingFile, + `Building ${o.relativeFilename}`)}); static INFO_BuildingFile = SevInfo | 0x0002; static Error_FileDoesNotExist = (o:{filename:string}) => m(this.ERROR_FileDoesNotExist, @@ -29,13 +29,13 @@ export class InfrastructureMessages { static ERROR_OutFileNotValidForProjects = SevError | 0x0005; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_FileBuiltSuccessfully = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_FileBuiltSuccessfully, - `${o.filename} built successfully.`)}); + static Info_FileBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_FileBuiltSuccessfully, + `${o.relativeFilename} built successfully.`)}); static INFO_FileBuiltSuccessfully = SevInfo | 0x0006; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_FileNotBuiltSuccessfully = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_FileNotBuiltSuccessfully, - `${o.filename} failed to build.`)}); + static Info_FileNotBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_FileNotBuiltSuccessfully, + `${o.relativeFilename} failed to build.`)}); static INFO_FileNotBuiltSuccessfully = SevInfo | 0x0007; static Error_InvalidProjectFile = (o:{message:string}) => m(this.ERROR_InvalidProjectFile, @@ -51,13 +51,13 @@ export class InfrastructureMessages { static ERROR_UnknownFileFormat = SevError | 0x000A; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_ProjectBuiltSuccessfully = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_ProjectBuiltSuccessfully, - `Project ${o.filename} built successfully.`)}); + static Info_ProjectBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_ProjectBuiltSuccessfully, + `Project ${o.relativeFilename} built successfully.`)}); static INFO_ProjectBuiltSuccessfully = SevInfo | 0x000B; // For this message, we override the filename with the passed-in file. A bit of a hack but does the job - static Info_ProjectNotBuiltSuccessfully = (o:{filename:string}) => ({filename:o.filename, ...m(this.INFO_ProjectNotBuiltSuccessfully, - `Project ${o.filename} failed to build.`)}); + static Info_ProjectNotBuiltSuccessfully = (o:{filename:string,relativeFilename:string}) => ({filename:o.filename, ...m(this.INFO_ProjectNotBuiltSuccessfully, + `Project ${o.relativeFilename} failed to build.`)}); static INFO_ProjectNotBuiltSuccessfully = SevInfo | 0x000C; static Info_TooManyMessages = (o:{count:number}) => m(this.INFO_TooManyMessages, diff --git a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts index d5e15e0bcc..015e00b9c1 100644 --- a/developer/src/kmc/src/util/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/util/NodeCompilerCallbacks.ts @@ -154,6 +154,28 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { event.filename = this.messageFilename; } + this.printMessage(event); + } + + private printMessage(event: CompilerEvent) { + if(this.options.logFormat == 'tsv') { + this.printTsvMessage(event); + } else { + this.printFormattedMessage(event); + } + } + + private printTsvMessage(event: CompilerEvent) { + process.stdout.write([ + CompilerError.formatFilename(event.filename, {fullPath:true, forwardSlashes:false}), + CompilerError.formatLine(event.line), + CompilerError.formatSeverity(event.code), + CompilerError.formatCode(event.code), + CompilerError.formatMessage(event.message) + ].join('\t') + '\n'); + } + + private printFormattedMessage(event: CompilerEvent) { const severityColor = severityColors[CompilerError.severity(event.code)] ?? color.reset; const messageColor = this.messageSpecialColor(event) ?? color.reset; process.stdout.write( @@ -172,7 +194,6 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { // Special case: we'll add a blank line after project builds process.stdout.write('\n'); } - } /** diff --git a/developer/src/kmc/src/util/baseOptions.ts b/developer/src/kmc/src/util/baseOptions.ts index a53539a124..67c8103294 100644 --- a/developer/src/kmc/src/util/baseOptions.ts +++ b/developer/src/kmc/src/util/baseOptions.ts @@ -1,4 +1,4 @@ -import { ALL_COMPILER_LOG_LEVELS } from "@keymanapp/common-types"; +import { ALL_COMPILER_LOG_FORMATS, ALL_COMPILER_LOG_LEVELS } from "@keymanapp/common-types"; import { Command, Option } from "commander"; import KEYMAN_VERSION from "@keymanapp/keyman-version"; @@ -23,6 +23,10 @@ export class BaseOptions { return program.addOption(new Option('-l, --log-level ', 'Log level').choices(ALL_COMPILER_LOG_LEVELS).default('info')); } + public static addLogFormat(program: Command) { + return program.addOption(new Option('-l, --log-format ', 'Log format').choices(ALL_COMPILER_LOG_FORMATS).default('formatted')); + } + public static addOutFile(program: Command) { return program.option('-o, --out-file ', 'Override the default path and filename for the output file') } @@ -31,6 +35,7 @@ export class BaseOptions { return [ this.addVersion, this.addLogLevel, + this.addLogFormat, this.addOutFile, ].reduce((p,f) => f(p), program); }