Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: format command to convert asyncapi document to multiple format #1549

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
10 changes: 7 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

121 changes: 121 additions & 0 deletions src/commands/format.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
import { promises as fPromises } from 'fs';
import { Args } from '@oclif/core';
import Command from '../core/base';

import {
convertToJSON,
convertToYaml,
load,
retrieveFileFormat,
} from '../core/models/SpecificationFile';
import { SpecificationWrongFileFormat } from '../core/errors/specification-file';
import { cyan, green } from 'picocolors';
import {
convertFormatFlags,
fileFormat,
} from '../core/flags/format.flags';

export default class Convert extends Command {
static specFile: any;
static metricsMetadata: any = {};
static description =
'Convert asyncapi documents from any format to yaml, yml or JSON';

static flags = convertFormatFlags();

static args = {
'spec-file': Args.string({
description: 'spec path, url, or context-name',
required: false,
}),
};

async run() {
const { args, flags } = await this.parse(Convert);
const filePath = args['spec-file'];
const outputFileFormat = flags['format'] as fileFormat;
let convertedFile;
try {
this.specFile = await load(filePath);
// eslint-disable-next-line sonarjs/no-duplicate-string
this.metricsMetadata.to_version = flags['target-version'];

const ff = retrieveFileFormat(this.specFile.text());
const isSpecFileJson = ff == 'json';
const isSpecFileYaml = ff == 'yaml';

if (!isSpecFileJson && !isSpecFileYaml) {
throw new SpecificationWrongFileFormat(filePath);
}

convertedFile = this.handleConversion(
isSpecFileJson,
isSpecFileYaml,
outputFileFormat,
);

if (!convertedFile) return;
await this.handleOutput(flags.output, convertedFile, outputFileFormat);
} catch (err) {
this.error(err as Error);
}
}

private handleConversion(
isSpecFileJson: boolean,
isSpecFileYaml: boolean,
outputFileFormat: fileFormat,
): string | undefined {
const text = this.specFile?.text();
if (isSpecFileJson && text) {
if (outputFileFormat == 'json') {
throw new Error(`Your document is already a ${cyan('JSON')}`);
}
return convertToYaml(text);
}
if (isSpecFileYaml && text) {
if (outputFileFormat == 'yaml' || outputFileFormat == 'yml') {
throw new Error(`Your document is already a ${cyan('YAML')}`);
}
return convertToJSON(text);
}
}

private async handleOutput(
outputPath: string | undefined,
formattedFile: string,
outputFileFormat: fileFormat,
) {
if (outputPath) {
outputPath = this.removeExtensionFromOutputPath(outputPath);
try {
const finalFileName = `${outputPath}.${outputFileFormat}`;
await fPromises.writeFile(finalFileName, formattedFile, {
encoding: 'utf8',
});
this.log(`converted to ${outputFileFormat} at ${green(finalFileName)}`);
} catch (err) {}
} else {
this.log(formattedFile);
}
}

private removeExtensionFromOutputPath(filename: string): string {
// Removes the extension from a filename if it is .json, .yaml, or .yml
// this is so that we can remove the provided extension name in the -o flag and
// apply our own extension name according to the content of the file
const validExtensions = ['json', 'yaml', 'yml'];

const parts = filename.split('.');

if (parts.length > 1) {
const extension = parts.pop()?.toLowerCase();
if (extension && validExtensions.includes(extension)) {
return parts.join('.');
}
}

return filename;
}
}
7 changes: 7 additions & 0 deletions src/core/errors/specification-file.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ export class SpecificationFileNotFound extends SpecificationFileError {
}
}

export class SpecificationWrongFileFormat extends SpecificationFileError {
constructor(filePath?: string) {
super();
this.message = `File ${filePath} is not of correct format.`;
}
}

export class SpecificationURLNotFound extends SpecificationFileError {
constructor(URL: string) {
super();
Expand Down
22 changes: 22 additions & 0 deletions src/core/flags/format.flags.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Flags } from '@oclif/core';

export type fileFormat = 'yaml' | 'yml' | 'json';

const availFileFormats: fileFormat[] = ['yaml', 'yml', 'json'];

export const convertFormatFlags = () => {
return {
help: Flags.help({ char: 'h' }),
output: Flags.string({
char: 'o',
description: 'path to the file where the result is saved',
}),
format: Flags.string({
char: 'f',
description: 'Specify the format to convert to',
options: availFileFormats,
required: true,
default: 'json',
}),
};
};
37 changes: 37 additions & 0 deletions src/core/models/SpecificationFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import yaml from 'js-yaml';
import { loadContext } from './Context';
import { ErrorLoadingSpec } from '../errors/specification-file';
import { MissingContextFileError } from '../errors/context-error';
import { fileFormat } from 'core/flags/format.flags';

const { readFile, lstat } = fs;
const allowedFileNames: string[] = [
Expand Down Expand Up @@ -222,3 +223,39 @@ async function detectSpecFile(): Promise<string | undefined> {
}));
return existingFileNames.find(filename => filename !== undefined);
}

export function retrieveFileFormat(content: string): fileFormat | undefined {
try {
if (content.trimStart()[0] === '{') {
JSON.parse(content);
return 'json';
}
// below yaml.load is not a definitive way to determine if a file is yaml or not.
// it is able to load .txt text files also.
yaml.load(content);
return 'yaml';
} catch (err) {
return undefined;
}
}

export function convertToYaml(spec: string) {
try {
// JS object -> YAML string
const jsonContent = yaml.load(spec);
return yaml.dump(jsonContent);
} catch (err) {
console.error(err);
}
}

export function convertToJSON(spec: string) {
try {
// JSON or YAML String -> JS object
const jsonContent = yaml.load(spec);
// JS Object -> pretty JSON string
return JSON.stringify(jsonContent, null, 2);
} catch (err) {
console.error(err);
}
}
Loading