Skip to content

Commit

Permalink
feat: format command to convert asyncapi document to multiple format (#…
Browse files Browse the repository at this point in the history
…1549)

Co-authored-by: Souvik De <[email protected]>
  • Loading branch information
catosaurusrex2003 and Souvikns authored Nov 18, 2024
1 parent 758df9d commit 260507f
Show file tree
Hide file tree
Showing 8 changed files with 18,549 additions and 0 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,9 @@ node_modules
test.asyncapi-cli
asyncapi.json
test/fixtures/minimaltemplate/__transpiled
test/fixtures/specification-conv.yml
test/fixtures/specification-conv.yaml
test/fixtures/specification-conv.json
.vscode

/action/
Expand Down
3 changes: 3 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"singleQuote": true
}
18,148 changes: 18,148 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

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);
const finalFileName = `${outputPath}.${outputFileFormat}`;
await fPromises.writeFile(finalFileName, formattedFile, {
encoding: 'utf8',
});
this.log(
`succesfully formatted to ${outputFileFormat} at ${green(finalFileName)} ✅`,
);
} else {
this.log(formattedFile);
this.log(`succesfully logged after formatting to ${outputFileFormat} ✅`);
}
}

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' }),
format: Flags.string({
char: 'f',
description: 'Specify the format to convert to',
options: availFileFormats,
required: true,
default: 'json',
}),
output: Flags.string({
char: 'o',
description: 'path to the file where the result is saved',
}),
};
};
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

0 comments on commit 260507f

Please sign in to comment.