UNPKG

8.98 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3exports.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 */
10const ts_types_1 = require("@salesforce/ts-types");
11const ts_types_2 = require("@salesforce/ts-types");
12const ts_types_3 = require("@salesforce/ts-types");
13const 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 */
72class 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}
241exports.DocOpts = DocOpts;
242//# sourceMappingURL=docOpts.js.map
\No newline at end of file