budgie-cli
Version:
Node CLI for Budgie.
171 lines (144 loc) • 5.73 kB
text/typescript
import * as commander from "commander";
import { ExitCode } from "../codes";
import { FileSystem, IFileSystem } from "../fileSystem";
import { ILogger, parseVerbosity, wrapLoggerForVerbosity } from "../logger";
import { IMain, main } from "../main";
import { globAllAsync, IGlobAllAsync } from "../utils/glob";
import { defaultValue } from "../utils/values";
import { getExcludes } from "./exclude";
import { printCliVersions } from "./version";
/**
* Dependencies to run the CLI.
*/
export interface ICliDependencies {
/**
* Raw argv-style string args from a command-line.
*/
argv: ReadonlyArray<string>;
/**
* System to read and write files, if not an fs-based default.
*/
fileSystem?: IFileSystem;
/**
* Finds file names from glob patterns, if not a glob-based default.
*/
globber?: IGlobAllAsync;
/**
* Logs information, if not the console.
*/
logger?: ILogger;
/**
* Main method to pass parsed arguments into.
*/
main?: IMain;
}
/**
* Commander-parsed arguments passed in.
*/
interface IParsedArguments {
/**
* Raw args to be used as file globs.
*/
args: ReadonlyArray<string>;
/**
* Base or root directory to ignore from the beginning of file paths, such as "src/", if not "".
*/
baseDirectory?: string;
/**
* File glob(s) to exclude.
*/
exclude?: string | ReadonlyArray<string>;
/**
* Output language(s) to convert to.
*/
language?: string | ReadonlyArray<string>;
/**
* Budgie configuration project, if provided.
*/
project?: string;
/**
* Namespace before path names, such as "Budgie", if not "".
*/
namespace?: string;
/**
* TypeScript configuration project, if provided.
*/
tsconfig?: string;
/**
* Minimum importance level of logs to print.
*/
verbosity?: string;
/**
* Displays help text via the logger.
*/
help(): void;
}
/**
* Parses raw string arguments and, if they're valid, calls to a main method.
*
* @param dependencies Raw string arguments and any system dependency overrides.
* @returns Promise for the result of the main method.
*/
export const cli = async (dependencies: ICliDependencies): Promise<ExitCode> => {
const { argv } = dependencies;
const fileSystem = defaultValue(dependencies.fileSystem, () => new FileSystem());
const globber = defaultValue(dependencies.globber, () => globAllAsync);
const logger = defaultValue(dependencies.logger, () => console);
const mainExecutor = defaultValue(dependencies.main, () => main);
const command = new commander.Command()
.usage("[options] <file ...> --language [language]")
.option("-b, --base-directory [base-directory]", "base directory to ignore from the beginning of file paths")
.option("-e, --exclude [exclude...]", "file glob(s) to exclude")
.option("-l, --language [language...]", "language(s) to convert to")
.option("-l, --project [project]", "budgie.json project metadata file")
.option("-n, --namespace [namespace]", "namespace before output path names")
.option("-t, --tsconfig [tsconfig]", "(TypeScript only) configuration project")
.option("-v, --verbosity [verbosity]", `Minimum logged verbosity level: "error" (default) or "log"`)
.option("-V, --version", "output the CLI and Budgie version numbers")
.on("--help", (): void => {
logger.log();
logger.log(" Basic Budgie conversion:");
logger.log();
logger.log(" $ budgie --language Python file.bg");
logger.log();
logger.log(" Converting a TypeScript project to Budgie, then to Python and Ruby:");
logger.log();
logger.log(" $ budgie --language Python --language Ruby --tsconfig ./tsconfig ./*.ts");
logger.log();
logger.log(" Converting a TypeScript project to Budgie, then to C#, replacing the 'src' path with 'Budgie':");
logger.log();
logger.log(" $ budgie --base-directory src/ --language C# --namespace Budgie --tsconfig ./tsconfig ./**/*.ts");
logger.log();
})
.parse(argv as string[]) as IParsedArguments;
if ({}.hasOwnProperty.call(command, "version")) {
await printCliVersions(logger);
return ExitCode.Ok;
}
if (command.args.length === 0) {
command.help();
return ExitCode.Ok;
}
const [includes, excludes] = await Promise.all([globber(command.args), getExcludes(command.exclude, globber)]);
const filePaths = new Set(includes);
for (const exclude of excludes) {
filePaths.delete(exclude);
}
const languageNames = command.language !== undefined && typeof command.language === "string" ? [command.language] : command.language;
const project = command.project === "false" ? undefined : command.project === undefined ? "budgie.json" : command.project;
const verbosity = parseVerbosity(command.verbosity);
if (verbosity === undefined) {
logger.error(`Unknown verbosity requested: '${command.verbosity}'.`);
return ExitCode.Error;
}
return mainExecutor({
baseDirectory: command.baseDirectory,
filePaths,
fileSystem,
languageNames,
logger: wrapLoggerForVerbosity(logger, verbosity),
namespace: command.namespace,
project,
typescriptConfig: command.tsconfig,
});
};