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