UNPKG

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