clerc 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -12,7 +12,15 @@ class SingleCommandError extends Error {
12
12
  super("Single command mode enabled.");
13
13
  }
14
14
  }
15
+ class SingleCommandAliasError extends Error {
16
+ constructor() {
17
+ super("Single command cannot have alias.");
18
+ }
19
+ }
15
20
  class CommandExistsError extends Error {
21
+ constructor(name) {
22
+ super(`Command "${name}" exists.`);
23
+ }
16
24
  }
17
25
  class CommonCommandExistsError extends Error {
18
26
  constructor() {
@@ -34,32 +42,44 @@ class SubcommandExistsError extends Error {
34
42
  super(`Command "${name}" cannot exist with its subcommand`);
35
43
  }
36
44
  }
37
-
38
- const resolveFlagAlias = (_command) => Object.entries((_command == null ? void 0 : _command.flags) || {}).reduce((acc, [name, command]) => {
39
- if (command.alias) {
40
- const item = utils.mustArray(command.alias).map(utils.kebabCase);
41
- acc[utils.kebabCase(name)] = item;
45
+ class MultipleCommandsMatchedError extends Error {
46
+ constructor(name) {
47
+ super(`Multiple commands matched: ${name}`);
42
48
  }
43
- return acc;
44
- }, {});
45
- const resolveFlagDefault = (_command) => Object.entries((_command == null ? void 0 : _command.flags) || {}).reduce((acc, [name, command]) => {
46
- const item = command.default;
47
- if (item) {
48
- acc[name] = item;
49
+ }
50
+ class CommandNameConflictError extends Error {
51
+ constructor(n1, n2) {
52
+ super(`Command name ${n1} conflicts with ${n2}. Maybe caused by alias.`);
49
53
  }
50
- return acc;
51
- }, {});
54
+ }
55
+
52
56
  function resolveCommand(commands, name) {
53
57
  if (name === SingleCommand) {
54
58
  return commands[SingleCommand];
55
59
  }
56
- const nameArr = Array.isArray(name) ? name : name.split(" ");
57
- const nameString = nameArr.join(" ");
58
- const possibleCommands = Object.values(commands).filter(
59
- (c) => utils.arrayStartsWith(nameArr, c.name.split(" ")) || utils.mustArray(c.alias || []).map(String).includes(nameString)
60
- );
60
+ const nameArr = utils.mustArray(name);
61
+ const commandsMap = /* @__PURE__ */ new Map();
62
+ for (const command of Object.values(commands)) {
63
+ if (command.alias) {
64
+ const aliases = utils.mustArray(command.alias);
65
+ for (const alias of aliases) {
66
+ if (alias in commands) {
67
+ throw new CommandNameConflictError(commands[alias].name, command.name);
68
+ }
69
+ commandsMap.set(alias.split(" "), command);
70
+ }
71
+ }
72
+ commandsMap.set(command.name.split(" "), command);
73
+ }
74
+ const possibleCommands = [];
75
+ commandsMap.forEach((v, k) => {
76
+ if (utils.arrayStartsWith(nameArr, k)) {
77
+ possibleCommands.push(v);
78
+ }
79
+ });
61
80
  if (possibleCommands.length > 1) {
62
- throw new Error(`Multiple commands found with name "${nameString}"`);
81
+ const matchedCommandNames = possibleCommands.map((c) => c.name).join(", ");
82
+ throw new MultipleCommandsMatchedError(matchedCommandNames);
63
83
  }
64
84
  return possibleCommands[0];
65
85
  }
@@ -71,7 +91,10 @@ function resolveSubcommandsByParent(commands, parent, depth = Infinity) {
71
91
  });
72
92
  }
73
93
  const resolveRootCommands = (commands) => resolveSubcommandsByParent(commands, "", 1);
74
- function resolveParametersBeforeFlag(argv) {
94
+ function resolveParametersBeforeFlag(argv, isSingleCommand) {
95
+ if (isSingleCommand) {
96
+ return [];
97
+ }
75
98
  const parameters = [];
76
99
  for (const arg of argv) {
77
100
  if (arg.startsWith("-")) {
@@ -92,6 +115,65 @@ function compose(inspectors) {
92
115
  };
93
116
  }
94
117
 
118
+ const { stringify } = JSON;
119
+ function parseParameters(parameters) {
120
+ const parsedParameters = [];
121
+ let hasOptional;
122
+ let hasSpread;
123
+ for (const parameter of parameters) {
124
+ if (hasSpread) {
125
+ throw new Error(`Invalid parameter: Spread parameter ${stringify(hasSpread)} must be last`);
126
+ }
127
+ const firstCharacter = parameter[0];
128
+ const lastCharacter = parameter[parameter.length - 1];
129
+ let required;
130
+ if (firstCharacter === "<" && lastCharacter === ">") {
131
+ required = true;
132
+ if (hasOptional) {
133
+ throw new Error(`Invalid parameter: Required parameter ${stringify(parameter)} cannot come after optional parameter ${stringify(hasOptional)}`);
134
+ }
135
+ }
136
+ if (firstCharacter === "[" && lastCharacter === "]") {
137
+ required = false;
138
+ hasOptional = parameter;
139
+ }
140
+ if (required === void 0) {
141
+ throw new Error(`Invalid parameter: ${stringify(parameter)}. Must be wrapped in <> (required parameter) or [] (optional parameter)`);
142
+ }
143
+ let name = parameter.slice(1, -1);
144
+ const spread = name.slice(-3) === "...";
145
+ if (spread) {
146
+ hasSpread = parameter;
147
+ name = name.slice(0, -3);
148
+ }
149
+ parsedParameters.push({
150
+ name,
151
+ required,
152
+ spread
153
+ });
154
+ }
155
+ return parsedParameters;
156
+ }
157
+ function mapParametersToArguments(mapping, parameters, cliArguments) {
158
+ for (let i = 0; i < parameters.length; i += 1) {
159
+ const { name, required, spread } = parameters[i];
160
+ const camelCaseName = utils.camelCase(name);
161
+ if (camelCaseName in mapping) {
162
+ throw new Error(`Invalid parameter: ${stringify(name)} is used more than once.`);
163
+ }
164
+ const value = spread ? cliArguments.slice(i) : cliArguments[i];
165
+ if (spread) {
166
+ i = parameters.length;
167
+ }
168
+ if (required && (!value || spread && value.length === 0)) {
169
+ console.error(`Error: Missing required parameter ${stringify(name)}
170
+ `);
171
+ return process.exit(1);
172
+ }
173
+ mapping[camelCaseName] = value;
174
+ }
175
+ }
176
+
95
177
  var __accessCheck = (obj, member, msg) => {
96
178
  if (!member.has(obj))
97
179
  throw TypeError("Cannot " + msg);
@@ -153,12 +235,14 @@ const _Clerc = class {
153
235
  __privateSet(this, _version, version);
154
236
  return this;
155
237
  }
156
- command(name, description, options = {}) {
238
+ command(nameOrCommand, description, options) {
239
+ const checkIsCommandObject = (nameOrCommand2) => !(typeof nameOrCommand2 === "string" || nameOrCommand2 === SingleCommand);
240
+ const isCommandObject = checkIsCommandObject(nameOrCommand);
241
+ const name = !isCommandObject ? nameOrCommand : nameOrCommand.name;
157
242
  if (__privateGet(this, _commands)[name]) {
158
243
  if (name === SingleCommand) {
159
- throw new CommandExistsError("Single command already exists");
244
+ throw new CommandExistsError("SingleCommand");
160
245
  }
161
- throw new CommandExistsError(`Command "${name === SingleCommand ? "[SingleCommand]" : name}" already exists`);
162
246
  }
163
247
  if (__privateGet(this, _isSingleCommand, isSingleCommand_get)) {
164
248
  throw new SingleCommandError();
@@ -166,6 +250,9 @@ const _Clerc = class {
166
250
  if (name === SingleCommand && __privateGet(this, _hasCommands, hasCommands_get)) {
167
251
  throw new CommonCommandExistsError();
168
252
  }
253
+ if (name === SingleCommand && (isCommandObject ? nameOrCommand : options).alias) {
254
+ throw new SingleCommandAliasError();
255
+ }
169
256
  if (name !== SingleCommand) {
170
257
  const splitedName = name.split(" ");
171
258
  const existedCommandNames = Object.keys(__privateGet(this, _commands)).filter((name2) => typeof name2 === "string").map((name2) => name2.split(" "));
@@ -176,7 +263,10 @@ const _Clerc = class {
176
263
  throw new SubcommandExistsError(splitedName.join(" "));
177
264
  }
178
265
  }
179
- __privateGet(this, _commands)[name] = { name, description, ...options };
266
+ __privateGet(this, _commands)[name] = !isCommandObject ? { name, description, ...options } : nameOrCommand;
267
+ if (isCommandObject && nameOrCommand.handler) {
268
+ this.on(nameOrCommand.name, nameOrCommand.handler);
269
+ }
180
270
  return this;
181
271
  }
182
272
  on(name, handler) {
@@ -191,23 +281,48 @@ const _Clerc = class {
191
281
  return this;
192
282
  }
193
283
  parse(argv = resolveArgv()) {
194
- const name = resolveParametersBeforeFlag(argv);
284
+ const name = resolveParametersBeforeFlag(argv, __privateGet(this, _isSingleCommand, isSingleCommand_get));
195
285
  const stringName = name.join(" ");
196
286
  const getCommand = () => __privateGet(this, _isSingleCommand, isSingleCommand_get) ? __privateGet(this, _commands)[SingleCommand] : resolveCommand(__privateGet(this, _commands), name);
197
287
  const getContext = () => {
198
288
  const command = getCommand();
199
289
  const isCommandResolved = !!command;
200
- const parsedWithType = typeFlag.typeFlag((command == null ? void 0 : command.flags) || {}, [...argv]);
201
- const { _: args, flags } = parsedWithType;
202
- const parameters = __privateGet(this, _isSingleCommand, isSingleCommand_get) || !isCommandResolved ? args : args.slice(command.name.split(" ").length);
290
+ const parsed = typeFlag.typeFlag((command == null ? void 0 : command.flags) || {}, [...argv]);
291
+ const { _: args, flags } = parsed;
292
+ let parameters = __privateGet(this, _isSingleCommand, isSingleCommand_get) || !isCommandResolved ? args : args.slice(command.name.split(" ").length);
293
+ let commandParameters = (command == null ? void 0 : command.parameters) || [];
294
+ const hasEof = commandParameters.indexOf("--");
295
+ const eofParameters = commandParameters.slice(hasEof + 1) || [];
296
+ const mapping = /* @__PURE__ */ Object.create(null);
297
+ if (hasEof > -1 && eofParameters.length > 0) {
298
+ commandParameters = commandParameters.slice(0, hasEof);
299
+ const eofArguments = parsed._["--"];
300
+ parameters = parameters.slice(0, -eofArguments.length || void 0);
301
+ mapParametersToArguments(
302
+ mapping,
303
+ parseParameters(commandParameters),
304
+ parameters
305
+ );
306
+ mapParametersToArguments(
307
+ mapping,
308
+ parseParameters(eofParameters),
309
+ eofArguments
310
+ );
311
+ } else {
312
+ mapParametersToArguments(
313
+ mapping,
314
+ parseParameters(commandParameters),
315
+ parameters
316
+ );
317
+ }
203
318
  const context = {
204
319
  name: command == null ? void 0 : command.name,
205
320
  resolved: isCommandResolved,
206
321
  isSingleCommand: __privateGet(this, _isSingleCommand, isSingleCommand_get),
207
- raw: parsedWithType,
208
- parameters,
322
+ raw: parsed,
323
+ parameters: mapping,
209
324
  flags,
210
- unknownFlags: parsedWithType.unknownFlags,
325
+ unknownFlags: parsed.unknownFlags,
211
326
  cli: this
212
327
  };
213
328
  return context;
@@ -245,23 +360,30 @@ hasCommands_get = function() {
245
360
  const definePlugin = (p) => p;
246
361
  const defineHandler = (_cli, _key, handler) => handler;
247
362
  const defineInspector = (_cli, inspector) => inspector;
363
+ const defineCommand = (c) => c;
364
+ const defineCommandWithHandler = (c) => c;
248
365
 
249
366
  exports.Clerc = Clerc;
250
367
  exports.CommandExistsError = CommandExistsError;
368
+ exports.CommandNameConflictError = CommandNameConflictError;
251
369
  exports.CommonCommandExistsError = CommonCommandExistsError;
370
+ exports.MultipleCommandsMatchedError = MultipleCommandsMatchedError;
252
371
  exports.NoSuchCommandError = NoSuchCommandError;
253
372
  exports.ParentCommandExistsError = ParentCommandExistsError;
254
373
  exports.SingleCommand = SingleCommand;
374
+ exports.SingleCommandAliasError = SingleCommandAliasError;
255
375
  exports.SingleCommandError = SingleCommandError;
256
376
  exports.SubcommandExistsError = SubcommandExistsError;
257
377
  exports.compose = compose;
378
+ exports.defineCommand = defineCommand;
379
+ exports.defineCommandWithHandler = defineCommandWithHandler;
258
380
  exports.defineHandler = defineHandler;
259
381
  exports.defineInspector = defineInspector;
260
382
  exports.definePlugin = definePlugin;
383
+ exports.mapParametersToArguments = mapParametersToArguments;
384
+ exports.parseParameters = parseParameters;
261
385
  exports.resolveArgv = resolveArgv;
262
386
  exports.resolveCommand = resolveCommand;
263
- exports.resolveFlagAlias = resolveFlagAlias;
264
- exports.resolveFlagDefault = resolveFlagDefault;
265
387
  exports.resolveParametersBeforeFlag = resolveParametersBeforeFlag;
266
388
  exports.resolveRootCommands = resolveRootCommands;
267
389
  exports.resolveSubcommandsByParent = resolveSubcommandsByParent;
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as _clerc_utils from '@clerc/utils';
2
- import { MaybeArray, Dict, LiteralUnion } from '@clerc/utils';
2
+ import { MaybeArray, Dict, CamelCase, LiteralUnion } from '@clerc/utils';
3
3
 
4
4
  declare const DOUBLE_DASH = "--";
5
5
  type TypeFunction<ReturnType = any> = (value: any) => ReturnType;
@@ -76,16 +76,22 @@ type FlagOptions = FlagSchema & {
76
76
  type Flag = FlagOptions & {
77
77
  name: string;
78
78
  };
79
- interface CommandOptions<A extends MaybeArray<string> = MaybeArray<string>, F extends Dict<FlagOptions> = Dict<FlagOptions>> {
79
+ interface CommandOptions<P extends string[] = string[], A extends MaybeArray<string> = MaybeArray<string>, F extends Dict<FlagOptions> = Dict<FlagOptions>> {
80
80
  alias?: A;
81
+ parameters?: P;
81
82
  flags?: F;
82
83
  examples?: [string, string][];
83
84
  notes?: string[];
84
85
  }
85
- type Command<N extends string | SingleCommandType = string, D extends string = string, Options extends CommandOptions = CommandOptions> = Options & {
86
+ type Command<N extends string | SingleCommandType = string, D extends string = string, O extends CommandOptions = CommandOptions> = O & {
86
87
  name: N;
87
88
  description: D;
88
89
  };
90
+ type CommandWithHandler<N extends string | SingleCommandType = string, D extends string = string, O extends CommandOptions = CommandOptions> = Command<N, D, O> & {
91
+ handler?: HandlerInCommand<Record<N, Command<N, D, O>>, N>;
92
+ };
93
+ type StripBrackets<Parameter extends string> = (Parameter extends `<${infer ParameterName}>` | `[${infer ParameterName}]` ? (ParameterName extends `${infer SpreadName}...` ? SpreadName : ParameterName) : never);
94
+ type ParameterType<Parameter extends string> = (Parameter extends `<${infer _ParameterName}...>` | `[${infer _ParameterName}...]` ? string[] : Parameter extends `<${infer _ParameterName}>` ? string : Parameter extends `[${infer _ParameterName}]` ? string | undefined : never);
89
95
  type CommandRecord = Dict<Command> & {
90
96
  [SingleCommand]?: Command;
91
97
  };
@@ -94,17 +100,23 @@ type MakeEventMap<T extends CommandRecord> = {
94
100
  };
95
101
  type PossibleInputKind = string | number | boolean | Dict<any>;
96
102
  type NonNullableFlag<T extends Dict<FlagOptions> | undefined> = T extends undefined ? {} : NonNullable<T>;
103
+ type NonNullableParameters<T extends string[] | undefined> = T extends undefined ? [] : NonNullable<T>;
97
104
  interface HandlerContext<C extends CommandRecord = CommandRecord, N extends keyof C = keyof C> {
98
105
  name?: N;
99
106
  resolved: boolean;
100
107
  isSingleCommand: boolean;
101
108
  raw: ParsedFlags;
102
- parameters: PossibleInputKind[];
109
+ parameters: {
110
+ [Parameter in [...NonNullableParameters<C[N]["parameters"]>][number] as CamelCase<StripBrackets<Parameter>>]: ParameterType<Parameter>;
111
+ };
103
112
  unknownFlags: ParsedFlags["unknownFlags"];
104
113
  flags: TypeFlag<NonNullableFlag<C[N]["flags"]>>["flags"];
105
114
  cli: Clerc<C>;
106
115
  }
107
116
  type Handler<C extends CommandRecord = CommandRecord, K extends keyof C = keyof C> = (ctx: HandlerContext<C, K>) => void;
117
+ type HandlerInCommand<C extends CommandRecord = CommandRecord, K extends keyof C = keyof C> = (ctx: HandlerContext<C, K> & {
118
+ name: K;
119
+ }) => void;
108
120
  type FallbackType<T, U> = {} extends T ? U : T;
109
121
  type InspectorContext<C extends CommandRecord = CommandRecord> = HandlerContext<C> & {
110
122
  flags: FallbackType<TypeFlag<NonNullableFlag<C[keyof C]["flags"]>>["flags"], Dict<any>>;
@@ -196,7 +208,8 @@ declare class Clerc<C extends CommandRecord = {}> {
196
208
  * })
197
209
  * ```
198
210
  */
199
- command<N extends string | SingleCommandType, D extends string, O extends CommandOptions>(name: N, description: D, options?: O): this & Clerc<C & Record<N, Command<N, D, O>>>;
211
+ command<N extends string | SingleCommandType, D extends string, O extends CommandOptions<[...P], A, F>, P extends string[] = string[], A extends MaybeArray<string> = MaybeArray<string>, F extends Dict<FlagOptions> = Dict<FlagOptions>>(c: CommandWithHandler<N, D, O & CommandOptions<[...P], A, F>>): this & Clerc<C & Record<N, Command<N, D, O>>>;
212
+ command<N extends string | SingleCommandType, D extends string, P extends string[], O extends CommandOptions<[...P]>>(name: N, description: D, options: O & CommandOptions<[...P]>): this & Clerc<C & Record<N, Command<N, D, O>>>;
200
213
  /**
201
214
  * Register a handler
202
215
  * @param name
@@ -252,12 +265,18 @@ declare class Clerc<C extends CommandRecord = {}> {
252
265
 
253
266
  declare const definePlugin: <T extends Clerc<{}>, U extends Clerc<{}>>(p: Plugin<T, U>) => Plugin<T, U>;
254
267
  declare const defineHandler: <C extends Clerc<{}>, K extends keyof C["_commands"]>(_cli: C, _key: K, handler: Handler<C["_commands"], K>) => Handler<C["_commands"], K>;
255
- declare const defineInspector: <C extends Clerc<{}>>(_cli: C, inspector: Inspector<C["_commands"]>) => Inspector<C["_commands"]>;
268
+ declare const defineInspector: <C extends Clerc<{}>>(_cli: C, inspector: Inspector<C["_commands"]>) => Inspector<C["_commands"]>;
269
+ declare const defineCommand: <N extends string | typeof SingleCommand, D extends string, P extends string[], O extends CommandOptions<[...P], MaybeArray<string>, Dict<FlagOptions>>>(c: Command<N, D, O>) => Command<N, D, O>;
270
+ declare const defineCommandWithHandler: <N extends string | typeof SingleCommand, D extends string, O extends CommandOptions<[...P], A, F>, P extends string[] = string[], A extends MaybeArray<string> = MaybeArray<string>, F extends Dict<FlagOptions> = Dict<FlagOptions>>(c: CommandWithHandler<N, D, O & CommandOptions<[...P], A, F>>) => CommandWithHandler<N, D, O & CommandOptions<[...P], A, F>>;
256
271
 
257
272
  declare class SingleCommandError extends Error {
258
273
  constructor();
259
274
  }
275
+ declare class SingleCommandAliasError extends Error {
276
+ constructor();
277
+ }
260
278
  declare class CommandExistsError extends Error {
279
+ constructor(name: string);
261
280
  }
262
281
  declare class CommonCommandExistsError extends Error {
263
282
  constructor();
@@ -270,15 +289,27 @@ declare class ParentCommandExistsError extends Error {
270
289
  }
271
290
  declare class SubcommandExistsError extends Error {
272
291
  constructor(name: string);
292
+ }
293
+ declare class MultipleCommandsMatchedError extends Error {
294
+ constructor(name: string);
295
+ }
296
+ declare class CommandNameConflictError extends Error {
297
+ constructor(n1: string, n2: string);
273
298
  }
274
299
 
275
- declare const resolveFlagAlias: (_command: Command) => Dict<string[]>;
276
- declare const resolveFlagDefault: (_command: Command) => Dict<any>;
277
300
  declare function resolveCommand(commands: CommandRecord, name: string | string[] | SingleCommandType): Command | undefined;
278
- declare function resolveSubcommandsByParent(commands: CommandRecord, parent: string | string[], depth?: number): Command<string, string, CommandOptions<_clerc_utils.MaybeArray<string>, Dict<FlagOptions>>>[];
279
- declare const resolveRootCommands: (commands: CommandRecord) => Command<string, string, CommandOptions<_clerc_utils.MaybeArray<string>, Dict<FlagOptions>>>[];
280
- declare function resolveParametersBeforeFlag(argv: string[]): string[];
301
+ declare function resolveSubcommandsByParent(commands: CommandRecord, parent: string | string[], depth?: number): Command<string, string, CommandOptions<string[], _clerc_utils.MaybeArray<string>, _clerc_utils.Dict<FlagOptions>>>[];
302
+ declare const resolveRootCommands: (commands: CommandRecord) => Command<string, string, CommandOptions<string[], _clerc_utils.MaybeArray<string>, _clerc_utils.Dict<FlagOptions>>>[];
303
+ declare function resolveParametersBeforeFlag(argv: string[], isSingleCommand: boolean): string[];
281
304
  declare const resolveArgv: () => string[];
282
305
  declare function compose(inspectors: Inspector[]): (getCtx: () => InspectorContext) => void;
283
306
 
284
- export { Clerc, Command, CommandExistsError, CommandOptions, CommandRecord, CommonCommandExistsError, FallbackType, Flag, FlagOptions, Handler, HandlerContext, Inspector, InspectorContext, MakeEventMap, NoSuchCommandError, ParentCommandExistsError, Plugin, PossibleInputKind, SingleCommand, SingleCommandError, SingleCommandType, SubcommandExistsError, compose, defineHandler, defineInspector, definePlugin, resolveArgv, resolveCommand, resolveFlagAlias, resolveFlagDefault, resolveParametersBeforeFlag, resolveRootCommands, resolveSubcommandsByParent };
307
+ interface ParsedParameter {
308
+ name: string;
309
+ required: boolean;
310
+ spread: boolean;
311
+ }
312
+ declare function parseParameters(parameters: string[]): ParsedParameter[];
313
+ declare function mapParametersToArguments(mapping: Record<string, string | string[]>, parameters: ParsedParameter[], cliArguments: string[]): undefined;
314
+
315
+ export { Clerc, Command, CommandExistsError, CommandNameConflictError, CommandOptions, CommandRecord, CommandWithHandler, CommonCommandExistsError, FallbackType, Flag, FlagOptions, Handler, HandlerContext, HandlerInCommand, Inspector, InspectorContext, MakeEventMap, MultipleCommandsMatchedError, NoSuchCommandError, ParentCommandExistsError, Plugin, PossibleInputKind, SingleCommand, SingleCommandAliasError, SingleCommandError, SingleCommandType, SubcommandExistsError, compose, defineCommand, defineCommandWithHandler, defineHandler, defineInspector, definePlugin, mapParametersToArguments, parseParameters, resolveArgv, resolveCommand, resolveParametersBeforeFlag, resolveRootCommands, resolveSubcommandsByParent };
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import { LiteEmit } from 'lite-emit';
2
2
  import { typeFlag } from 'type-flag';
3
- import { mustArray, kebabCase, arrayStartsWith } from '@clerc/utils';
3
+ import { mustArray, arrayStartsWith, camelCase } from '@clerc/utils';
4
4
  import { isNode, isDeno } from 'is-platform';
5
5
 
6
6
  class SingleCommandError extends Error {
@@ -8,7 +8,15 @@ class SingleCommandError extends Error {
8
8
  super("Single command mode enabled.");
9
9
  }
10
10
  }
11
+ class SingleCommandAliasError extends Error {
12
+ constructor() {
13
+ super("Single command cannot have alias.");
14
+ }
15
+ }
11
16
  class CommandExistsError extends Error {
17
+ constructor(name) {
18
+ super(`Command "${name}" exists.`);
19
+ }
12
20
  }
13
21
  class CommonCommandExistsError extends Error {
14
22
  constructor() {
@@ -30,32 +38,44 @@ class SubcommandExistsError extends Error {
30
38
  super(`Command "${name}" cannot exist with its subcommand`);
31
39
  }
32
40
  }
41
+ class MultipleCommandsMatchedError extends Error {
42
+ constructor(name) {
43
+ super(`Multiple commands matched: ${name}`);
44
+ }
45
+ }
46
+ class CommandNameConflictError extends Error {
47
+ constructor(n1, n2) {
48
+ super(`Command name ${n1} conflicts with ${n2}. Maybe caused by alias.`);
49
+ }
50
+ }
33
51
 
34
- const resolveFlagAlias = (_command) => Object.entries((_command == null ? void 0 : _command.flags) || {}).reduce((acc, [name, command]) => {
35
- if (command.alias) {
36
- const item = mustArray(command.alias).map(kebabCase);
37
- acc[kebabCase(name)] = item;
38
- }
39
- return acc;
40
- }, {});
41
- const resolveFlagDefault = (_command) => Object.entries((_command == null ? void 0 : _command.flags) || {}).reduce((acc, [name, command]) => {
42
- const item = command.default;
43
- if (item) {
44
- acc[name] = item;
45
- }
46
- return acc;
47
- }, {});
48
52
  function resolveCommand(commands, name) {
49
53
  if (name === SingleCommand) {
50
54
  return commands[SingleCommand];
51
55
  }
52
- const nameArr = Array.isArray(name) ? name : name.split(" ");
53
- const nameString = nameArr.join(" ");
54
- const possibleCommands = Object.values(commands).filter(
55
- (c) => arrayStartsWith(nameArr, c.name.split(" ")) || mustArray(c.alias || []).map(String).includes(nameString)
56
- );
56
+ const nameArr = mustArray(name);
57
+ const commandsMap = /* @__PURE__ */ new Map();
58
+ for (const command of Object.values(commands)) {
59
+ if (command.alias) {
60
+ const aliases = mustArray(command.alias);
61
+ for (const alias of aliases) {
62
+ if (alias in commands) {
63
+ throw new CommandNameConflictError(commands[alias].name, command.name);
64
+ }
65
+ commandsMap.set(alias.split(" "), command);
66
+ }
67
+ }
68
+ commandsMap.set(command.name.split(" "), command);
69
+ }
70
+ const possibleCommands = [];
71
+ commandsMap.forEach((v, k) => {
72
+ if (arrayStartsWith(nameArr, k)) {
73
+ possibleCommands.push(v);
74
+ }
75
+ });
57
76
  if (possibleCommands.length > 1) {
58
- throw new Error(`Multiple commands found with name "${nameString}"`);
77
+ const matchedCommandNames = possibleCommands.map((c) => c.name).join(", ");
78
+ throw new MultipleCommandsMatchedError(matchedCommandNames);
59
79
  }
60
80
  return possibleCommands[0];
61
81
  }
@@ -67,7 +87,10 @@ function resolveSubcommandsByParent(commands, parent, depth = Infinity) {
67
87
  });
68
88
  }
69
89
  const resolveRootCommands = (commands) => resolveSubcommandsByParent(commands, "", 1);
70
- function resolveParametersBeforeFlag(argv) {
90
+ function resolveParametersBeforeFlag(argv, isSingleCommand) {
91
+ if (isSingleCommand) {
92
+ return [];
93
+ }
71
94
  const parameters = [];
72
95
  for (const arg of argv) {
73
96
  if (arg.startsWith("-")) {
@@ -88,6 +111,65 @@ function compose(inspectors) {
88
111
  };
89
112
  }
90
113
 
114
+ const { stringify } = JSON;
115
+ function parseParameters(parameters) {
116
+ const parsedParameters = [];
117
+ let hasOptional;
118
+ let hasSpread;
119
+ for (const parameter of parameters) {
120
+ if (hasSpread) {
121
+ throw new Error(`Invalid parameter: Spread parameter ${stringify(hasSpread)} must be last`);
122
+ }
123
+ const firstCharacter = parameter[0];
124
+ const lastCharacter = parameter[parameter.length - 1];
125
+ let required;
126
+ if (firstCharacter === "<" && lastCharacter === ">") {
127
+ required = true;
128
+ if (hasOptional) {
129
+ throw new Error(`Invalid parameter: Required parameter ${stringify(parameter)} cannot come after optional parameter ${stringify(hasOptional)}`);
130
+ }
131
+ }
132
+ if (firstCharacter === "[" && lastCharacter === "]") {
133
+ required = false;
134
+ hasOptional = parameter;
135
+ }
136
+ if (required === void 0) {
137
+ throw new Error(`Invalid parameter: ${stringify(parameter)}. Must be wrapped in <> (required parameter) or [] (optional parameter)`);
138
+ }
139
+ let name = parameter.slice(1, -1);
140
+ const spread = name.slice(-3) === "...";
141
+ if (spread) {
142
+ hasSpread = parameter;
143
+ name = name.slice(0, -3);
144
+ }
145
+ parsedParameters.push({
146
+ name,
147
+ required,
148
+ spread
149
+ });
150
+ }
151
+ return parsedParameters;
152
+ }
153
+ function mapParametersToArguments(mapping, parameters, cliArguments) {
154
+ for (let i = 0; i < parameters.length; i += 1) {
155
+ const { name, required, spread } = parameters[i];
156
+ const camelCaseName = camelCase(name);
157
+ if (camelCaseName in mapping) {
158
+ throw new Error(`Invalid parameter: ${stringify(name)} is used more than once.`);
159
+ }
160
+ const value = spread ? cliArguments.slice(i) : cliArguments[i];
161
+ if (spread) {
162
+ i = parameters.length;
163
+ }
164
+ if (required && (!value || spread && value.length === 0)) {
165
+ console.error(`Error: Missing required parameter ${stringify(name)}
166
+ `);
167
+ return process.exit(1);
168
+ }
169
+ mapping[camelCaseName] = value;
170
+ }
171
+ }
172
+
91
173
  var __accessCheck = (obj, member, msg) => {
92
174
  if (!member.has(obj))
93
175
  throw TypeError("Cannot " + msg);
@@ -149,12 +231,14 @@ const _Clerc = class {
149
231
  __privateSet(this, _version, version);
150
232
  return this;
151
233
  }
152
- command(name, description, options = {}) {
234
+ command(nameOrCommand, description, options) {
235
+ const checkIsCommandObject = (nameOrCommand2) => !(typeof nameOrCommand2 === "string" || nameOrCommand2 === SingleCommand);
236
+ const isCommandObject = checkIsCommandObject(nameOrCommand);
237
+ const name = !isCommandObject ? nameOrCommand : nameOrCommand.name;
153
238
  if (__privateGet(this, _commands)[name]) {
154
239
  if (name === SingleCommand) {
155
- throw new CommandExistsError("Single command already exists");
240
+ throw new CommandExistsError("SingleCommand");
156
241
  }
157
- throw new CommandExistsError(`Command "${name === SingleCommand ? "[SingleCommand]" : name}" already exists`);
158
242
  }
159
243
  if (__privateGet(this, _isSingleCommand, isSingleCommand_get)) {
160
244
  throw new SingleCommandError();
@@ -162,6 +246,9 @@ const _Clerc = class {
162
246
  if (name === SingleCommand && __privateGet(this, _hasCommands, hasCommands_get)) {
163
247
  throw new CommonCommandExistsError();
164
248
  }
249
+ if (name === SingleCommand && (isCommandObject ? nameOrCommand : options).alias) {
250
+ throw new SingleCommandAliasError();
251
+ }
165
252
  if (name !== SingleCommand) {
166
253
  const splitedName = name.split(" ");
167
254
  const existedCommandNames = Object.keys(__privateGet(this, _commands)).filter((name2) => typeof name2 === "string").map((name2) => name2.split(" "));
@@ -172,7 +259,10 @@ const _Clerc = class {
172
259
  throw new SubcommandExistsError(splitedName.join(" "));
173
260
  }
174
261
  }
175
- __privateGet(this, _commands)[name] = { name, description, ...options };
262
+ __privateGet(this, _commands)[name] = !isCommandObject ? { name, description, ...options } : nameOrCommand;
263
+ if (isCommandObject && nameOrCommand.handler) {
264
+ this.on(nameOrCommand.name, nameOrCommand.handler);
265
+ }
176
266
  return this;
177
267
  }
178
268
  on(name, handler) {
@@ -187,23 +277,48 @@ const _Clerc = class {
187
277
  return this;
188
278
  }
189
279
  parse(argv = resolveArgv()) {
190
- const name = resolveParametersBeforeFlag(argv);
280
+ const name = resolveParametersBeforeFlag(argv, __privateGet(this, _isSingleCommand, isSingleCommand_get));
191
281
  const stringName = name.join(" ");
192
282
  const getCommand = () => __privateGet(this, _isSingleCommand, isSingleCommand_get) ? __privateGet(this, _commands)[SingleCommand] : resolveCommand(__privateGet(this, _commands), name);
193
283
  const getContext = () => {
194
284
  const command = getCommand();
195
285
  const isCommandResolved = !!command;
196
- const parsedWithType = typeFlag((command == null ? void 0 : command.flags) || {}, [...argv]);
197
- const { _: args, flags } = parsedWithType;
198
- const parameters = __privateGet(this, _isSingleCommand, isSingleCommand_get) || !isCommandResolved ? args : args.slice(command.name.split(" ").length);
286
+ const parsed = typeFlag((command == null ? void 0 : command.flags) || {}, [...argv]);
287
+ const { _: args, flags } = parsed;
288
+ let parameters = __privateGet(this, _isSingleCommand, isSingleCommand_get) || !isCommandResolved ? args : args.slice(command.name.split(" ").length);
289
+ let commandParameters = (command == null ? void 0 : command.parameters) || [];
290
+ const hasEof = commandParameters.indexOf("--");
291
+ const eofParameters = commandParameters.slice(hasEof + 1) || [];
292
+ const mapping = /* @__PURE__ */ Object.create(null);
293
+ if (hasEof > -1 && eofParameters.length > 0) {
294
+ commandParameters = commandParameters.slice(0, hasEof);
295
+ const eofArguments = parsed._["--"];
296
+ parameters = parameters.slice(0, -eofArguments.length || void 0);
297
+ mapParametersToArguments(
298
+ mapping,
299
+ parseParameters(commandParameters),
300
+ parameters
301
+ );
302
+ mapParametersToArguments(
303
+ mapping,
304
+ parseParameters(eofParameters),
305
+ eofArguments
306
+ );
307
+ } else {
308
+ mapParametersToArguments(
309
+ mapping,
310
+ parseParameters(commandParameters),
311
+ parameters
312
+ );
313
+ }
199
314
  const context = {
200
315
  name: command == null ? void 0 : command.name,
201
316
  resolved: isCommandResolved,
202
317
  isSingleCommand: __privateGet(this, _isSingleCommand, isSingleCommand_get),
203
- raw: parsedWithType,
204
- parameters,
318
+ raw: parsed,
319
+ parameters: mapping,
205
320
  flags,
206
- unknownFlags: parsedWithType.unknownFlags,
321
+ unknownFlags: parsed.unknownFlags,
207
322
  cli: this
208
323
  };
209
324
  return context;
@@ -241,5 +356,7 @@ hasCommands_get = function() {
241
356
  const definePlugin = (p) => p;
242
357
  const defineHandler = (_cli, _key, handler) => handler;
243
358
  const defineInspector = (_cli, inspector) => inspector;
359
+ const defineCommand = (c) => c;
360
+ const defineCommandWithHandler = (c) => c;
244
361
 
245
- export { Clerc, CommandExistsError, CommonCommandExistsError, NoSuchCommandError, ParentCommandExistsError, SingleCommand, SingleCommandError, SubcommandExistsError, compose, defineHandler, defineInspector, definePlugin, resolveArgv, resolveCommand, resolveFlagAlias, resolveFlagDefault, resolveParametersBeforeFlag, resolveRootCommands, resolveSubcommandsByParent };
362
+ export { Clerc, CommandExistsError, CommandNameConflictError, CommonCommandExistsError, MultipleCommandsMatchedError, NoSuchCommandError, ParentCommandExistsError, SingleCommand, SingleCommandAliasError, SingleCommandError, SubcommandExistsError, compose, defineCommand, defineCommandWithHandler, defineHandler, defineInspector, definePlugin, mapParametersToArguments, parseParameters, resolveArgv, resolveCommand, resolveParametersBeforeFlag, resolveRootCommands, resolveSubcommandsByParent };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clerc",
3
- "version": "0.6.0",
3
+ "version": "0.7.0",
4
4
  "author": "Ray <nn_201312@163.com> (https://github.com/so1ve)",
5
5
  "description": "Clerc is a simple and easy-to-use cli framework.",
6
6
  "keywords": [
@@ -39,7 +39,7 @@
39
39
  "access": "public"
40
40
  },
41
41
  "dependencies": {
42
- "@clerc/utils": "0.6.0",
42
+ "@clerc/utils": "0.7.0",
43
43
  "is-platform": "^0.2.0",
44
44
  "lite-emit": "^1.4.0",
45
45
  "type-flag": "^3.0.0"