1 | ;
|
2 | /*
|
3 | * Copyright (c) 2020, salesforce.com, inc.
|
4 | * All rights reserved.
|
5 | * Licensed under the BSD 3-Clause license.
|
6 | * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
|
7 | */
|
8 | Object.defineProperty(exports, "__esModule", { value: true });
|
9 | exports.DocOpts = void 0;
|
10 | /* eslint-disable @typescript-eslint/prefer-nullish-coalescing */
|
11 | const ts_types_1 = require("@salesforce/ts-types");
|
12 | const ts_types_2 = require("@salesforce/ts-types");
|
13 | const ts_types_3 = require("@salesforce/ts-types");
|
14 | const sfdxFlags_1 = require("./sfdxFlags");
|
15 | /**
|
16 | * DocOpts generator for SfdxCommands. See http://docopt.org/.
|
17 | *
|
18 | * flag.exclusive: groups elements when one of the mutually exclusive cases is a required flag: (--apple | --orange)
|
19 | * flag.exclusive: groups elements when none of the mutually exclusive cases is required (optional flags): [--apple | --orange]
|
20 | * flag.dependsOn: specifies that if one element is present, then another one is required: (--apple --orange)
|
21 | * cmd.variableArgs: produces 'name=value'
|
22 | *
|
23 | * @example
|
24 | * {
|
25 | * name: 'classnames',
|
26 | * required: true,
|
27 | * exclusive: ['suitenames']
|
28 | * ...
|
29 | * },{
|
30 | * name: 'suitenames',
|
31 | * type: 'array'
|
32 | * required: true
|
33 | * ...
|
34 | * }
|
35 | *
|
36 | * Results in:
|
37 | * Usage: <%= command.id %> (-n <string> | -s <array>)
|
38 | *
|
39 | * @example
|
40 | * {
|
41 | * name: 'classnames',
|
42 | * ...
|
43 | * excludes: ['suitenames']
|
44 | * },{
|
45 | * name: 'suitenames',
|
46 | * ...
|
47 | * }
|
48 | *
|
49 | * Results in:
|
50 | * Usage: <%= command.id %> [-n <string> | -s <string>]
|
51 | *
|
52 | * @example
|
53 | * {
|
54 | * name: 'classnames',
|
55 | * ...
|
56 | * dependsOn: ['suitenames']
|
57 | * },{
|
58 | * name: 'suitenames',
|
59 | * type: 'flag'
|
60 | * ...
|
61 | * }
|
62 | *
|
63 | * Results in:
|
64 | * Usage: <%= command.id %> (-n <string> -s)
|
65 | *
|
66 | * TODO:
|
67 | * - Support nesting, eg:
|
68 | * Usage: my_program (--either-this <and-that> | <or-this>)
|
69 | * Usage: my_program [(<one-argument> <another-argument>)]
|
70 | *
|
71 | * @param cmdDef
|
72 | */
|
73 | class DocOpts {
|
74 | constructor(cmd) {
|
75 | this.cmd = cmd;
|
76 | // Create a new map with references to the flags that we can manipulate.
|
77 | this.flags = {};
|
78 | // @ts-ignore
|
79 | this.flagList = (0, ts_types_2.definiteEntriesOf)(this.cmd.flags)
|
80 | .filter(([, v]) => !v.hidden)
|
81 | .map(([k, v]) => {
|
82 | const { description, ...rest } = v;
|
83 | const flag = { description: (0, ts_types_1.ensure)(description), ...rest, name: k };
|
84 | // @ts-ignore
|
85 | this.flags[k] = flag;
|
86 | return flag;
|
87 | });
|
88 | }
|
89 | static generate(cmdDef) {
|
90 | return new DocOpts(cmdDef).toString();
|
91 | }
|
92 | toString() {
|
93 | try {
|
94 | const groups = Object.values(this.groupFlagElements());
|
95 | // Protected field
|
96 | const varargs = this.cmd.getVarArgsConfig();
|
97 | let varargsElement = '';
|
98 | if (varargs) {
|
99 | varargsElement = 'name=value...';
|
100 | const isRequired = (0, ts_types_1.isPlainObject)(varargs) && varargs.required;
|
101 | if (!isRequired) {
|
102 | varargsElement = `[${varargsElement}]`;
|
103 | }
|
104 | varargsElement = `${varargsElement} `;
|
105 | }
|
106 | return `<%= command.id %> ${varargsElement}${groups.join(' ')}`;
|
107 | }
|
108 | catch (e) {
|
109 | // If there is an error, just return no usage so we don't fail command help.
|
110 | return '';
|
111 | }
|
112 | }
|
113 | /**
|
114 | * Group flags that dependOn (and) and are exclusive (or).
|
115 | */
|
116 | groupFlagElements() {
|
117 | const groups = this.categorizeFlags();
|
118 | const elementMap = {};
|
119 | // Generate all doc opt elements for combining
|
120 | this.generateElements(elementMap, groups.requiredFlags);
|
121 | this.generateElements(elementMap, groups.optionalFlags);
|
122 | this.generateElements(elementMap, groups.sometimesBuiltinFlags);
|
123 | this.generateElements(elementMap, groups.alwaysBuiltinFlags);
|
124 | for (const flag of this.flagList) {
|
125 | if ((0, ts_types_1.isArray)(flag.dependsOn)) {
|
126 | this.combineElementsToFlag(elementMap, flag.name, flag.dependsOn, ' ');
|
127 | }
|
128 | if ((0, ts_types_1.isArray)(flag.exclusive)) {
|
129 | this.combineElementsToFlag(elementMap, flag.name, flag.exclusive, ' | ');
|
130 | }
|
131 | }
|
132 | // Since combineElementsToFlag deletes the references in this.flags when it combines
|
133 | // them, this will go through the remaining list of uncombined elements.
|
134 | for (const remainingFlagName of Object.keys(this.flags)) {
|
135 | const remainingFlag = (0, ts_types_1.ensure)(this.flags[remainingFlagName]);
|
136 | if (!remainingFlag.required) {
|
137 | elementMap[remainingFlag.name] = `[${elementMap[remainingFlag.name] ?? ''}]`;
|
138 | }
|
139 | }
|
140 | return elementMap;
|
141 | }
|
142 | /**
|
143 | * Combine doc opt elements to another flag's doc opt element. This is for supporting
|
144 | * things like "and" (dependsOn) and "or" (exclusive).
|
145 | *
|
146 | * This will probably break down on complex dependsOn / exclusive flag structures.
|
147 | * For example, a flag that depends on a flag that depends on another flag.
|
148 | *
|
149 | * See tests to see what is supported.
|
150 | *
|
151 | * @param elementMap All doc opt elements.
|
152 | * @param flagName The name of the flag to combine to.
|
153 | * @param flagNames The other flag names to combine to flagName.
|
154 | * @param unionString How to combine the doc opt elements.
|
155 | */
|
156 | combineElementsToFlag(elementMap, flagName, flagNames, unionString) {
|
157 | if (!this.flags[flagName]) {
|
158 | return;
|
159 | }
|
160 | let isRequired = (0, ts_types_1.ensure)(this.flags[flagName]).required;
|
161 | if (!(0, ts_types_1.isBoolean)(isRequired) || !isRequired) {
|
162 | isRequired = flagNames.reduce((required, toCombine) => required || this.cmd.flags[toCombine].required || false, false);
|
163 | }
|
164 | for (const toCombine of flagNames) {
|
165 | elementMap[flagName] = `${elementMap[flagName] ?? ''}${unionString}${elementMap[toCombine] ?? ''}`;
|
166 | // We handled this flag, don't handle it again
|
167 | delete elementMap[toCombine];
|
168 | delete this.flags[toCombine];
|
169 | }
|
170 | if (isRequired) {
|
171 | elementMap[flagName] = `(${elementMap[flagName] ?? ''})`;
|
172 | }
|
173 | else {
|
174 | elementMap[flagName] = `[${elementMap[flagName] ?? ''}]`;
|
175 | }
|
176 | // We handled this flag, don't handle it again
|
177 | delete this.flags[flagName];
|
178 | }
|
179 | /**
|
180 | * Categorize flags into required, optional, builtin opt-in, and mandatory builtin
|
181 | * flags. This is the order they should appear in the doc opts.
|
182 | *
|
183 | * For example, flags defined on the actual command should some before standard
|
184 | * fields like --json.
|
185 | */
|
186 | categorizeFlags() {
|
187 | const alwaysBuiltinFlags = [];
|
188 | const alwaysBuiltinFlagKeys = Object.keys(sfdxFlags_1.requiredBuiltinFlags);
|
189 | const sometimesBuiltinFlags = [];
|
190 | const sometimesBuiltinFlagKeys = Object.keys(sfdxFlags_1.optionalBuiltinFlags);
|
191 | const requiredFlags = [];
|
192 | const optionalFlags = [];
|
193 | // We should also group on depends (AND, OR)
|
194 | for (const flag of this.flagList) {
|
195 | if (alwaysBuiltinFlagKeys.find((key) => key === flag.name)) {
|
196 | alwaysBuiltinFlags.push(flag);
|
197 | }
|
198 | else if (sometimesBuiltinFlagKeys.find((key) => key === flag.name)) {
|
199 | sometimesBuiltinFlags.push(flag);
|
200 | }
|
201 | else if (flag.required) {
|
202 | requiredFlags.push(flag);
|
203 | }
|
204 | else {
|
205 | optionalFlags.push(flag);
|
206 | }
|
207 | }
|
208 | return {
|
209 | requiredFlags,
|
210 | optionalFlags,
|
211 | sometimesBuiltinFlags,
|
212 | alwaysBuiltinFlags,
|
213 | };
|
214 | }
|
215 | /**
|
216 | * Generate doc opt elements for all flags.
|
217 | *
|
218 | * @param elementMap The map to add the elements to.
|
219 | * @param flagGroups The flags to generate elements for.
|
220 | */
|
221 | // eslint-disable-next-line class-methods-use-this
|
222 | generateElements(elementMap = {}, flagGroups) {
|
223 | const elementStrs = [];
|
224 | for (const flag of flagGroups) {
|
225 | const kind = (0, ts_types_1.ensure)((0, ts_types_1.getString)(flag, 'kind'));
|
226 | // not all flags have short names
|
227 | const flagName = flag.char ? `-${flag.char}` : `--${flag.name}`;
|
228 | let type = '';
|
229 | if (kind !== 'boolean') {
|
230 | if (kind === 'enum') {
|
231 | const options = (0, ts_types_1.ensureArray)((0, ts_types_3.get)(flag, 'options'));
|
232 | type = ` ${options.join('|')}`;
|
233 | }
|
234 | else {
|
235 | type = ` <${kind || 'string'}>`;
|
236 | }
|
237 | }
|
238 | const element = `${flagName}${type}`;
|
239 | elementMap[flag.name] = element;
|
240 | elementStrs.push(element);
|
241 | }
|
242 | return elementStrs;
|
243 | }
|
244 | }
|
245 | exports.DocOpts = DocOpts;
|
246 | //# sourceMappingURL=docOpts.js.map |
\ | No newline at end of file |