Skip to content

Commit 37864fa

Browse files
chore: Split CLI index into multiple files (#2762)
* fix lint errors * remove gratuitous lodash from test.ts * resolve lint issues in index * split CLIOptions interface to own file * split cli options, inference, option definitions, utils to own files * split sources to own file * move input, usage to own file * move quicktype options to own file * ♻️
1 parent 66bcaee commit 37864fa

File tree

10 files changed

+1283
-1173
lines changed

10 files changed

+1283
-1173
lines changed

src/CLIOptions.types.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import type { LanguageName, RendererOptions } from "quicktype-core";
2+
3+
export interface CLIOptions<Lang extends LanguageName = LanguageName> {
4+
// We use this to access the inference flags
5+
// biome-ignore lint/suspicious/noExplicitAny: <explanation>
6+
[option: string]: any;
7+
additionalSchema: string[];
8+
allPropertiesOptional: boolean;
9+
alphabetizeProperties: boolean;
10+
buildMarkovChain?: string;
11+
debug?: string;
12+
graphqlIntrospect?: string;
13+
graphqlSchema?: string;
14+
help: boolean;
15+
httpHeader?: string[];
16+
httpMethod?: string;
17+
lang: Lang;
18+
19+
noRender: boolean;
20+
out?: string;
21+
quiet: boolean;
22+
23+
rendererOptions: RendererOptions<Lang>;
24+
25+
src: string[];
26+
srcLang: string;
27+
srcUrls?: string;
28+
telemetry?: string;
29+
topLevel: string;
30+
31+
version: boolean;
32+
}

src/cli.options.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import { exceptionToString } from "@glideapps/ts-necessities";
2+
import {
3+
// biome-ignore lint/suspicious/noShadowRestrictedNames: <explanation>
4+
hasOwnProperty,
5+
} from "collection-utils";
6+
import commandLineArgs from "command-line-args";
7+
import _ from "lodash";
8+
9+
import {
10+
type OptionDefinition,
11+
type RendererOptions,
12+
type TargetLanguage,
13+
assert,
14+
defaultTargetLanguages,
15+
getTargetLanguage,
16+
isLanguageName,
17+
messageError,
18+
} from "quicktype-core";
19+
20+
import { inferCLIOptions } from "./inference";
21+
import { makeOptionDefinitions } from "./optionDefinitions";
22+
import type { CLIOptions } from "./CLIOptions.types";
23+
24+
// Parse the options in argv and split them into global options and renderer options,
25+
// according to each option definition's `renderer` field. If `partial` is false this
26+
// will throw if it encounters an unknown option.
27+
function parseOptions(
28+
definitions: OptionDefinition[],
29+
argv: string[],
30+
partial: boolean,
31+
): Partial<CLIOptions> {
32+
let opts: commandLineArgs.CommandLineOptions;
33+
try {
34+
opts = commandLineArgs(
35+
definitions.map((def) => ({
36+
...def,
37+
type: def.optionType === "boolean" ? Boolean : String,
38+
})),
39+
{ argv, partial },
40+
);
41+
} catch (e) {
42+
assert(!partial, "Partial option parsing should not have failed");
43+
return messageError("DriverCLIOptionParsingFailed", {
44+
message: exceptionToString(e),
45+
});
46+
}
47+
48+
for (const k of Object.keys(opts)) {
49+
if (opts[k] === null) {
50+
return messageError("DriverCLIOptionParsingFailed", {
51+
message: `Missing value for command line option "${k}"`,
52+
});
53+
}
54+
}
55+
56+
const options: {
57+
[key: string]: unknown;
58+
rendererOptions: RendererOptions;
59+
} = { rendererOptions: {} };
60+
for (const optionDefinition of definitions) {
61+
if (!hasOwnProperty(opts, optionDefinition.name)) {
62+
continue;
63+
}
64+
65+
const optionValue = opts[optionDefinition.name] as string;
66+
if (optionDefinition.kind !== "cli") {
67+
(
68+
options.rendererOptions as Record<
69+
typeof optionDefinition.name,
70+
unknown
71+
>
72+
)[optionDefinition.name] = optionValue;
73+
} else {
74+
const k = _.lowerFirst(
75+
optionDefinition.name.split("-").map(_.upperFirst).join(""),
76+
);
77+
options[k] = optionValue;
78+
}
79+
}
80+
81+
return options;
82+
}
83+
84+
export function parseCLIOptions(
85+
argv: string[],
86+
inputTargetLanguage?: TargetLanguage,
87+
): CLIOptions {
88+
if (argv.length === 0) {
89+
return inferCLIOptions({ help: true }, inputTargetLanguage);
90+
}
91+
92+
const targetLanguages = inputTargetLanguage
93+
? [inputTargetLanguage]
94+
: defaultTargetLanguages;
95+
const optionDefinitions = makeOptionDefinitions(targetLanguages);
96+
97+
// We can only fully parse the options once we know which renderer is selected,
98+
// because there are renderer-specific options. But we only know which renderer
99+
// is selected after we've parsed the options. Hence, we parse the options
100+
// twice. This is the first parse to get the renderer:
101+
const incompleteOptions = inferCLIOptions(
102+
parseOptions(optionDefinitions, argv, true),
103+
inputTargetLanguage,
104+
);
105+
106+
let targetLanguage = inputTargetLanguage as TargetLanguage;
107+
if (inputTargetLanguage === undefined) {
108+
const languageName = isLanguageName(incompleteOptions.lang)
109+
? incompleteOptions.lang
110+
: "typescript";
111+
targetLanguage = getTargetLanguage(languageName);
112+
}
113+
114+
const rendererOptionDefinitions =
115+
targetLanguage.cliOptionDefinitions.actual;
116+
// Use the global options as well as the renderer options from now on:
117+
const allOptionDefinitions = _.concat(
118+
optionDefinitions,
119+
rendererOptionDefinitions,
120+
);
121+
// This is the parse that counts:
122+
return inferCLIOptions(
123+
parseOptions(allOptionDefinitions, argv, false),
124+
targetLanguage,
125+
);
126+
}

0 commit comments

Comments
 (0)