Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
2 changes: 1 addition & 1 deletion packages/quicktype-core/src/Inference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export const inferenceFlagsObject = {
},
};
export type InferenceFlagName = keyof typeof inferenceFlagsObject;
export const inferenceFlagNames = Object.getOwnPropertyNames(
export const inferenceFlagNames = Object.keys(
inferenceFlagsObject,
) as InferenceFlagName[];
export const inferenceFlags: { [F in InferenceFlagName]: InferenceFlag } =
Expand Down
2 changes: 1 addition & 1 deletion packages/quicktype-core/src/support/Chance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ export class Chance {
return this.integer({ min: 0, max: options.max });
}

pick<T>(arr: T[]): T {
pick<T>(arr: readonly T[]): T {
if (arr.length === 0) {
throw new RangeError("Chance: Cannot pick() from an empty array");
}
Expand Down
45 changes: 45 additions & 0 deletions src/CLIOptions.types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import type {
LanguageName,
RendererOptions,
InferenceFlagName,
} from "quicktype-core";

type CamelToPascal<T extends string> =
T extends `${infer FirstChar}${infer Rest}`
? `${Capitalize<FirstChar>}${Rest}`
: never;

export type NegatedInferenceFlagName<
Input extends InferenceFlagName = InferenceFlagName,
> = `no${CamelToPascal<Input extends `infer${infer Name}` ? Name : Input>}`;

export interface CLIOptions<Lang extends LanguageName = LanguageName>
extends Partial<
Record<InferenceFlagName | NegatedInferenceFlagName, boolean>
> {
additionalSchema: string[];
allPropertiesOptional: boolean;
alphabetizeProperties: boolean;
buildMarkovChain?: string;
debug?: string;
graphqlIntrospect?: string;
graphqlSchema?: string;
help: boolean;
httpHeader?: string[];
httpMethod?: string;
lang: Lang;

noRender: boolean;
out?: string;
quiet: boolean;

rendererOptions: RendererOptions<Lang>;

src: string[];
srcLang: string;
srcUrls?: string;
telemetry?: "enable" | "disable";
topLevel: string;

version: boolean;
}
122 changes: 122 additions & 0 deletions src/cli.options.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { exceptionToString } from "@glideapps/ts-necessities";
import commandLineArgs from "command-line-args";
import _ from "lodash";

import {
type OptionDefinition,
type RendererOptions,
type TargetLanguage,
defaultTargetLanguages,
getTargetLanguage,
isLanguageName,
messageError,
} from "quicktype-core";

import { inferCLIOptions } from "./inference";
import {
makeOptionDefinitions,
transformDefinition,
} from "./optionDefinitions";
import type { CLIOptions } from "./CLIOptions.types";

// Parse the options in argv and split them into global options and renderer options,
// according to each option definition's `renderer` field. If `partial` is false this
// will throw if it encounters an unknown option.
export function parseOptions(
definitions: OptionDefinition[],
argv: string[],
partial: boolean,
): Partial<CLIOptions> {
let opts: commandLineArgs.CommandLineOptions;
try {
opts = commandLineArgs(definitions.map(transformDefinition), {
argv,
partial,
});
} catch (e) {
return messageError("DriverCLIOptionParsingFailed", {
message: exceptionToString(e),
});
}

for (const k of Object.keys(opts)) {
if (opts[k] === null) {
return messageError("DriverCLIOptionParsingFailed", {
message: `Missing value for command line option "${k}"`,
});
}
}

const options: {
[key: string]: unknown;
rendererOptions: RendererOptions;
} = { rendererOptions: {} };
for (const optionDefinition of definitions) {
if (!(optionDefinition.name in opts)) {
continue;
}

const optionValue = opts[optionDefinition.name] as string;
if (optionDefinition.kind !== "cli") {
(
options.rendererOptions as Record<
typeof optionDefinition.name,
unknown
>
)[optionDefinition.name] = optionValue;
}
// Inference flags
else {
const k = _.lowerFirst(
optionDefinition.name.split("-").map(_.upperFirst).join(""),
);
options[k] = optionValue;
}
}

return options;
}

export function parseCLIOptions(
argv: string[],
inputTargetLanguage?: TargetLanguage,
): CLIOptions {
if (argv.length === 0) {
return inferCLIOptions({ help: true }, inputTargetLanguage);
}

const targetLanguages = inputTargetLanguage
? [inputTargetLanguage]
: defaultTargetLanguages;
const optionDefinitions = makeOptionDefinitions(targetLanguages);

// We can only fully parse the options once we know which renderer is selected,
// because there are renderer-specific options. But we only know which renderer
// is selected after we've parsed the options. Hence, we parse the options
// twice. This is the first parse to get the renderer:
const incompleteOptions = inferCLIOptions(
parseOptions(optionDefinitions, argv, true),
inputTargetLanguage,
);

let targetLanguage = inputTargetLanguage as TargetLanguage;
if (inputTargetLanguage === undefined) {
const languageName = isLanguageName(incompleteOptions.lang)
? incompleteOptions.lang
: "typescript";
targetLanguage = getTargetLanguage(languageName);
}

const rendererOptionDefinitions =
targetLanguage.cliOptionDefinitions.actual;
// Use the global options as well as the renderer options from now on:
const allOptionDefinitions = _.concat(
optionDefinitions,
rendererOptionDefinitions,
);
// This is the parse that counts:
return inferCLIOptions(
parseOptions(allOptionDefinitions, argv, false),
targetLanguage,
);
}
Loading
Loading