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.
This commit is contained in:
Marc Durdin 2023-09-01 18:00:13 +04:00
parent 0d96437cb8
commit d2f4e5f2c7
7 changed files with 75 additions and 23 deletions

View file

@ -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';

View file

@ -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];

View file

@ -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})
);
}

View file

@ -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;

View file

@ -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,

View file

@ -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');
}
}
/**

View file

@ -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 <logLevel>', 'Log level').choices(ALL_COMPILER_LOG_LEVELS).default('info'));
}
public static addLogFormat(program: Command) {
return program.addOption(new Option('-l, --log-format <logFormat>', 'Log format').choices(ALL_COMPILER_LOG_FORMATS).default('formatted'));
}
public static addOutFile(program: Command) {
return program.option('-o, --out-file <filename>', '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);
}