@gunshi/plugin-completion 0.27.0-alpha.8 → 0.27.0-alpha.9
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/README.md +230 -4
- package/lib/index.d.ts +32 -6
- package/lib/index.js +474 -302
- package/package.json +11 -7
package/README.md
CHANGED
|
@@ -1,14 +1,240 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @gunshi/plugin-completion
|
|
2
2
|
|
|
3
|
-
> completion plugin for gunshi
|
|
3
|
+
> shell completion plugin for gunshi.
|
|
4
|
+
|
|
5
|
+
This plugin provides tab completion functionality for your CLI applications, allowing users to auto-complete commands, options, and arguments in their shell. It generates shell-specific completion scripts and handles runtime completion suggestions.
|
|
6
|
+
|
|
7
|
+
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
8
|
+
|
|
9
|
+
> [!WARNING]
|
|
10
|
+
> This package support Node.js runtime only. Deno and Bun support are coming soon.
|
|
11
|
+
|
|
12
|
+
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
13
|
+
|
|
14
|
+
## 💿 Installation
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
# npm
|
|
18
|
+
npm install --save @gunshi/plugin-completion
|
|
19
|
+
|
|
20
|
+
# pnpm
|
|
21
|
+
pnpm add @gunshi/plugin-completion
|
|
22
|
+
|
|
23
|
+
# yarn
|
|
24
|
+
yarn add @gunshi/plugin-completion
|
|
25
|
+
|
|
26
|
+
# deno
|
|
27
|
+
deno add jsr:@gunshi/plugin-completion
|
|
28
|
+
|
|
29
|
+
# bun
|
|
30
|
+
bun add @gunshi/plugin-completion
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 🚀 Usage
|
|
34
|
+
|
|
35
|
+
```ts
|
|
36
|
+
import { cli } from 'gunshi'
|
|
37
|
+
import completion from '@gunshi/plugin-completion'
|
|
38
|
+
|
|
39
|
+
const command = {
|
|
40
|
+
name: 'deploy',
|
|
41
|
+
args: {
|
|
42
|
+
environment: {
|
|
43
|
+
type: 'string',
|
|
44
|
+
short: 'e',
|
|
45
|
+
description: 'Target environment'
|
|
46
|
+
},
|
|
47
|
+
config: {
|
|
48
|
+
type: 'string',
|
|
49
|
+
short: 'c',
|
|
50
|
+
description: 'Config file path'
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
run: ctx => {
|
|
54
|
+
console.log(`Deploying to ${ctx.values.environment}`)
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
await cli(process.argv.slice(2), command, {
|
|
59
|
+
name: 'my-cli',
|
|
60
|
+
version: '1.0.0',
|
|
61
|
+
plugins: [
|
|
62
|
+
completion({
|
|
63
|
+
config: {
|
|
64
|
+
entry: {
|
|
65
|
+
args: {
|
|
66
|
+
config: {
|
|
67
|
+
handler: () => [
|
|
68
|
+
{ value: 'prod.json', description: 'Production config' },
|
|
69
|
+
{ value: 'dev.json', description: 'Development config' },
|
|
70
|
+
{ value: 'test.json', description: 'Test config' }
|
|
71
|
+
]
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
]
|
|
78
|
+
})
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## ✨ Features
|
|
82
|
+
|
|
83
|
+
### Automatic Complete Command
|
|
84
|
+
|
|
85
|
+
The plugin automatically adds a `complete` subcommand to your CLI:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Generate shell completion script
|
|
89
|
+
my-cli complete bash > ~/.my-cli-completion.bash
|
|
90
|
+
source ~/.my-cli-completion.bash
|
|
91
|
+
|
|
92
|
+
# Now tab completion works!
|
|
93
|
+
my-cli dep<TAB> # Completes to: my-cli deploy
|
|
94
|
+
my-cli deploy --env<TAB> # Completes to: my-cli deploy --environment
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### Shell Support
|
|
98
|
+
|
|
99
|
+
The `complete` command accepts the following shell types:
|
|
100
|
+
|
|
101
|
+
- `bash` - Bash shell completion
|
|
102
|
+
- `zsh` - Zsh shell completion
|
|
103
|
+
- `fish` - Fish shell completion
|
|
104
|
+
|
|
105
|
+
### Custom Completion Handlers
|
|
106
|
+
|
|
107
|
+
You can provide custom completion handlers for specific arguments:
|
|
108
|
+
|
|
109
|
+
```ts
|
|
110
|
+
completion({
|
|
111
|
+
config: {
|
|
112
|
+
entry: {
|
|
113
|
+
args: {
|
|
114
|
+
environment: {
|
|
115
|
+
handler: ({ locale }) => [
|
|
116
|
+
{ value: 'production', description: 'Production environment' },
|
|
117
|
+
{ value: 'staging', description: 'Staging environment' },
|
|
118
|
+
{ value: 'development', description: 'Development environment' }
|
|
119
|
+
]
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
},
|
|
123
|
+
subCommands: {
|
|
124
|
+
deploy: {
|
|
125
|
+
args: {
|
|
126
|
+
region: {
|
|
127
|
+
handler: ({ previousArgs }) => {
|
|
128
|
+
// Dynamic completions based on previous arguments
|
|
129
|
+
const env = previousArgs.find(arg => arg.startsWith('--environment='))
|
|
130
|
+
if (env?.includes('production')) {
|
|
131
|
+
return [
|
|
132
|
+
{ value: 'us-east-1', description: 'US East (N. Virginia)' },
|
|
133
|
+
{ value: 'eu-west-1', description: 'EU (Ireland)' }
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
return [{ value: 'local', description: 'Local development' }]
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
})
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
### Internationalization Support
|
|
147
|
+
|
|
148
|
+
When used with `@gunshi/plugin-i18n`, completion descriptions are automatically localized:
|
|
149
|
+
|
|
150
|
+
```ts
|
|
151
|
+
import completion from '@gunshi/plugin-completion'
|
|
152
|
+
import i18n from '@gunshi/plugin-i18n'
|
|
153
|
+
|
|
154
|
+
await cli(args, command, {
|
|
155
|
+
plugins: [
|
|
156
|
+
i18n({ locale: 'ja-JP' }),
|
|
157
|
+
completion() // Descriptions will be shown in Japanese
|
|
158
|
+
]
|
|
159
|
+
})
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
## ⚙️ Plugin Options
|
|
163
|
+
|
|
164
|
+
### `config`
|
|
165
|
+
|
|
166
|
+
- Type: `{ entry?: CompletionConfig, subCommands?: Record<string, CompletionConfig> }`
|
|
167
|
+
- Default: `{}`
|
|
168
|
+
- Description: Configuration for completion handlers
|
|
169
|
+
|
|
170
|
+
#### CompletionConfig
|
|
171
|
+
|
|
172
|
+
```ts
|
|
173
|
+
interface CompletionConfig {
|
|
174
|
+
handler?: CompletionHandler // Handler for command-level completions
|
|
175
|
+
args?: Record<
|
|
176
|
+
string,
|
|
177
|
+
{
|
|
178
|
+
// Handlers for specific arguments
|
|
179
|
+
handler: CompletionHandler
|
|
180
|
+
}
|
|
181
|
+
>
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
#### CompletionHandler
|
|
186
|
+
|
|
187
|
+
```ts
|
|
188
|
+
type CompletionHandler = (params: {
|
|
189
|
+
previousArgs: string[] // Previously entered arguments
|
|
190
|
+
toComplete: string // Current string being completed
|
|
191
|
+
endWithSpace: boolean // Whether input ends with space
|
|
192
|
+
locale?: Intl.Locale // Current locale (if i18n is enabled)
|
|
193
|
+
}) => CompletionItem[]
|
|
194
|
+
|
|
195
|
+
interface CompletionItem {
|
|
196
|
+
value: string // The completion value
|
|
197
|
+
description?: string // Optional description
|
|
198
|
+
}
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## 🔗 Plugin Dependencies
|
|
202
|
+
|
|
203
|
+
The completion plugin has an optional dependency on the i18n plugin:
|
|
204
|
+
|
|
205
|
+
- **Plugin ID**: `g:i18n` (optional)
|
|
206
|
+
- **Purpose**: Provides localized descriptions for completions
|
|
207
|
+
- **Effect**: When the i18n plugin is present, all command and argument descriptions are automatically translated to the current locale
|
|
208
|
+
|
|
209
|
+
## 🧩 Context Extensions
|
|
210
|
+
|
|
211
|
+
When using the completion plugin, your command context is extended via `ctx.extensions['g:completion']`.
|
|
4
212
|
|
|
5
213
|
<!-- eslint-disable markdown/no-missing-label-refs -->
|
|
6
214
|
|
|
7
|
-
> !
|
|
8
|
-
> This
|
|
215
|
+
> [!IMPORTANT]
|
|
216
|
+
> This plugin extension is namespaced in `CommandContext.extensions` using this plugin ID `g:completion` by the gunshi plugin system.
|
|
9
217
|
|
|
10
218
|
<!-- eslint-enable markdown/no-missing-label-refs -->
|
|
11
219
|
|
|
220
|
+
Currently, the completion plugin does not provide any context extensions for use within commands. The plugin ID can be imported for type-safe access:
|
|
221
|
+
|
|
222
|
+
```ts
|
|
223
|
+
import completion, { pluginId } from '@gunshi/plugin-completion'
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
## 📚 API References
|
|
227
|
+
|
|
228
|
+
See the [API References](./docs/index.md)
|
|
229
|
+
|
|
230
|
+
## 💖 Credits
|
|
231
|
+
|
|
232
|
+
This project uses and depends on:
|
|
233
|
+
|
|
234
|
+
- [`@bombsh/tab`](https://github.com/bombshell-dev/tab), created by [Bombshell](https://github.com/bombshell-dev) - Shell completion library
|
|
235
|
+
|
|
236
|
+
Thank you!
|
|
237
|
+
|
|
12
238
|
## ©️ License
|
|
13
239
|
|
|
14
240
|
[MIT](http://opensource.org/licenses/MIT)
|
package/lib/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
/*! license ISC
|
|
2
|
+
* @author Bombshell team and Bombshell contributors
|
|
3
|
+
* Bombshell related codes are forked from @bombsh/tab
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { PluginWithoutExtension } from "@gunshi/plugin";
|
|
3
7
|
|
|
4
8
|
//#region ../shared/src/constants.d.ts
|
|
5
9
|
/**
|
|
@@ -23,6 +27,15 @@ type GenerateNamespacedKey<Key extends string, Prefixed extends string = typeof
|
|
|
23
27
|
/**
|
|
24
28
|
* Command i18n built-in arguments keys.
|
|
25
29
|
*/
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/bombshell/index.d.ts
|
|
33
|
+
|
|
34
|
+
type Item = {
|
|
35
|
+
description: string;
|
|
36
|
+
value: string;
|
|
37
|
+
};
|
|
38
|
+
type Handler = (previousArgs: string[], toComplete: string, endsWithSpace: boolean) => Item[] | Promise<Item[]>;
|
|
26
39
|
//#endregion
|
|
27
40
|
//#region src/types.d.ts
|
|
28
41
|
/**
|
|
@@ -33,6 +46,19 @@ declare const pluginId: GenerateNamespacedKey<'completion', typeof PLUGIN_PREFIX
|
|
|
33
46
|
* Type representing the unique identifier for the completion plugin.
|
|
34
47
|
*/
|
|
35
48
|
type PluginId = typeof pluginId;
|
|
49
|
+
/**
|
|
50
|
+
* Parameters for {@link CompletionHandler | the completion handler}.
|
|
51
|
+
*/
|
|
52
|
+
interface CompletionParams {
|
|
53
|
+
previousArgs: Parameters<Handler>[0];
|
|
54
|
+
toComplete: Parameters<Handler>[1];
|
|
55
|
+
endWithSpace: Parameters<Handler>[2];
|
|
56
|
+
locale?: Intl.Locale;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* The handler for completion.
|
|
60
|
+
*/
|
|
61
|
+
type CompletionHandler = (params: CompletionParams) => ReturnType<Handler>;
|
|
36
62
|
/**
|
|
37
63
|
* Extended command context which provides utilities via completion plugin.
|
|
38
64
|
* These utilities are available via `CommandContext.extensions['g:completion']`.
|
|
@@ -42,9 +68,9 @@ interface CompletionCommandContext {}
|
|
|
42
68
|
* Completion configuration, which structure is similar `bombsh/tab`'s `CompletionConfig`.
|
|
43
69
|
*/
|
|
44
70
|
interface CompletionConfig {
|
|
45
|
-
handler?:
|
|
71
|
+
handler?: CompletionHandler;
|
|
46
72
|
args?: Record<string, {
|
|
47
|
-
handler:
|
|
73
|
+
handler: CompletionHandler;
|
|
48
74
|
}>;
|
|
49
75
|
}
|
|
50
76
|
/**
|
|
@@ -67,6 +93,6 @@ interface CompletionOptions {
|
|
|
67
93
|
/**
|
|
68
94
|
* completion plugin for gunshi
|
|
69
95
|
*/
|
|
70
|
-
declare function completion(options?: CompletionOptions):
|
|
96
|
+
declare function completion(options?: CompletionOptions): PluginWithoutExtension;
|
|
71
97
|
//#endregion
|
|
72
|
-
export { CompletionCommandContext, CompletionConfig, CompletionOptions, PluginId, completion as default, pluginId };
|
|
98
|
+
export { CompletionCommandContext, CompletionConfig, CompletionHandler, CompletionOptions, CompletionParams, PluginId, completion as default, pluginId };
|
package/lib/index.js
CHANGED
|
@@ -1,6 +1,195 @@
|
|
|
1
|
-
|
|
1
|
+
/*! license ISC
|
|
2
|
+
* @author Bombshell team and Bombshell contributors
|
|
3
|
+
* Bombshell related codes are forked from @bombsh/tab
|
|
4
|
+
*/
|
|
2
5
|
|
|
3
|
-
|
|
6
|
+
import { CLI_OPTIONS_DEFAULT, createCommandContext, plugin } from "@gunshi/plugin";
|
|
7
|
+
|
|
8
|
+
//#region ../../node_modules/.pnpm/args-tokens@0.22.0/node_modules/args-tokens/lib/utils-N7UlhLbz.js
|
|
9
|
+
/**
|
|
10
|
+
* Entry point of utils.
|
|
11
|
+
*
|
|
12
|
+
* Note that this entry point is used by gunshi to import utility functions.
|
|
13
|
+
*
|
|
14
|
+
* @module
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
18
|
+
* @license MIT
|
|
19
|
+
*/
|
|
20
|
+
function kebabnize(str) {
|
|
21
|
+
return str.replace(/[A-Z]/g, (match, offset) => (offset > 0 ? "-" : "") + match.toLowerCase());
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
//#endregion
|
|
25
|
+
//#region ../gunshi/src/utils.ts
|
|
26
|
+
function isLazyCommand(cmd) {
|
|
27
|
+
return typeof cmd === "function" && "commandName" in cmd && !!cmd.commandName;
|
|
28
|
+
}
|
|
29
|
+
async function resolveLazyCommand(cmd, name, needRunResolving = false) {
|
|
30
|
+
let command;
|
|
31
|
+
if (isLazyCommand(cmd)) {
|
|
32
|
+
const baseCommand = {
|
|
33
|
+
name: cmd.commandName,
|
|
34
|
+
description: cmd.description,
|
|
35
|
+
args: cmd.args,
|
|
36
|
+
examples: cmd.examples,
|
|
37
|
+
internal: cmd.internal,
|
|
38
|
+
entry: cmd.entry
|
|
39
|
+
};
|
|
40
|
+
if ("resource" in cmd && cmd.resource) baseCommand.resource = cmd.resource;
|
|
41
|
+
command = Object.assign(create(), baseCommand);
|
|
42
|
+
if (needRunResolving) {
|
|
43
|
+
const loaded = await cmd();
|
|
44
|
+
if (typeof loaded === "function") command.run = loaded;
|
|
45
|
+
else if (typeof loaded === "object") {
|
|
46
|
+
if (loaded.run == null) throw new TypeError(`'run' is required in command: ${cmd.name || name}`);
|
|
47
|
+
command.run = loaded.run;
|
|
48
|
+
command.name = loaded.name;
|
|
49
|
+
command.description = loaded.description;
|
|
50
|
+
command.args = loaded.args;
|
|
51
|
+
command.examples = loaded.examples;
|
|
52
|
+
command.internal = loaded.internal;
|
|
53
|
+
command.entry = loaded.entry;
|
|
54
|
+
if ("resource" in loaded && loaded.resource) command.resource = loaded.resource;
|
|
55
|
+
} else throw new TypeError(`Cannot resolve command: ${cmd.name || name}`);
|
|
56
|
+
}
|
|
57
|
+
} else command = Object.assign(create(), cmd);
|
|
58
|
+
if (command.name == null && name) command.name = name;
|
|
59
|
+
return deepFreeze(command);
|
|
60
|
+
}
|
|
61
|
+
function create(obj = null) {
|
|
62
|
+
return Object.create(obj);
|
|
63
|
+
}
|
|
64
|
+
function deepFreeze(obj, ignores = []) {
|
|
65
|
+
if (obj === null || typeof obj !== "object") return obj;
|
|
66
|
+
for (const key of Object.keys(obj)) {
|
|
67
|
+
const value = obj[key];
|
|
68
|
+
if (ignores.includes(key)) continue;
|
|
69
|
+
if (typeof value === "object" && value !== null) deepFreeze(value, ignores);
|
|
70
|
+
}
|
|
71
|
+
return Object.freeze(obj);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
//#endregion
|
|
75
|
+
//#region ../shared/src/constants.ts
|
|
76
|
+
/**
|
|
77
|
+
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
78
|
+
* @license MIT
|
|
79
|
+
*/
|
|
80
|
+
const BUILT_IN_PREFIX = "_";
|
|
81
|
+
const PLUGIN_PREFIX = "g";
|
|
82
|
+
const ARG_PREFIX = "arg";
|
|
83
|
+
const BUILT_IN_KEY_SEPARATOR = ":";
|
|
84
|
+
const BUILD_IN_PREFIX_AND_KEY_SEPARATOR = `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
|
|
85
|
+
const ARG_PREFIX_AND_KEY_SEPARATOR = `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
|
|
86
|
+
const ARG_NEGATABLE_PREFIX = "no-";
|
|
87
|
+
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region ../resources/locales/en-US.json
|
|
90
|
+
var COMMAND = "COMMAND";
|
|
91
|
+
var COMMANDS = "COMMANDS";
|
|
92
|
+
var SUBCOMMAND = "SUBCOMMAND";
|
|
93
|
+
var USAGE = "USAGE";
|
|
94
|
+
var ARGUMENTS = "ARGUMENTS";
|
|
95
|
+
var OPTIONS = "OPTIONS";
|
|
96
|
+
var EXAMPLES = "EXAMPLES";
|
|
97
|
+
var FORMORE = "For more info, run any command with the `--help` flag";
|
|
98
|
+
var NEGATABLE = "Negatable of";
|
|
99
|
+
var DEFAULT = "default";
|
|
100
|
+
var CHOICES = "choices";
|
|
101
|
+
var help = "Display this help message";
|
|
102
|
+
var version = "Display this version";
|
|
103
|
+
var en_US_default = {
|
|
104
|
+
COMMAND,
|
|
105
|
+
COMMANDS,
|
|
106
|
+
SUBCOMMAND,
|
|
107
|
+
USAGE,
|
|
108
|
+
ARGUMENTS,
|
|
109
|
+
OPTIONS,
|
|
110
|
+
EXAMPLES,
|
|
111
|
+
FORMORE,
|
|
112
|
+
NEGATABLE,
|
|
113
|
+
DEFAULT,
|
|
114
|
+
CHOICES,
|
|
115
|
+
help,
|
|
116
|
+
version
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
//#endregion
|
|
120
|
+
//#region ../shared/src/utils.ts
|
|
121
|
+
/**
|
|
122
|
+
* Resolve a namespaced key for argument resources.
|
|
123
|
+
* Argument keys are prefixed with "arg:".
|
|
124
|
+
* If the command name is provided, it will be prefixed with the command name (e.g. "cmd1:arg:foo").
|
|
125
|
+
* @param key The argument key to resolve.
|
|
126
|
+
* @param ctx The command context.
|
|
127
|
+
* @returns Prefixed argument key.
|
|
128
|
+
*/
|
|
129
|
+
function resolveArgKey(key, ctx) {
|
|
130
|
+
return `${ctx?.name ? `${ctx.name}${BUILT_IN_KEY_SEPARATOR}` : ""}${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}${key}`;
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* Resolve a namespaced key for non-built-in resources.
|
|
134
|
+
* Non-built-in keys are not prefixed with any special characters. If the command name is provided, it will be prefixed with the command name (e.g. "cmd1:foo").
|
|
135
|
+
* @param key The non-built-in key to resolve.
|
|
136
|
+
* @param ctx The command context.
|
|
137
|
+
* @returns Prefixed non-built-in key.
|
|
138
|
+
*/
|
|
139
|
+
function resolveKey(key, ctx) {
|
|
140
|
+
return `${ctx?.name ? `${ctx.name}${BUILT_IN_KEY_SEPARATOR}` : ""}${key}`;
|
|
141
|
+
}
|
|
142
|
+
async function resolveExamples(ctx, examples) {
|
|
143
|
+
return typeof examples === "string" ? examples : typeof examples === "function" ? await examples(ctx) : "";
|
|
144
|
+
}
|
|
145
|
+
function namespacedId(id) {
|
|
146
|
+
return `${PLUGIN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${id}`;
|
|
147
|
+
}
|
|
148
|
+
function makeShortLongOptionPair(schema, name, toKebab) {
|
|
149
|
+
const displayName = toKebab || schema.toKebab ? kebabnize(name) : name;
|
|
150
|
+
let key = `--${displayName}`;
|
|
151
|
+
if (schema.short) key = `-${schema.short}, ${key}`;
|
|
152
|
+
return key;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region ../shared/src/localization.ts
|
|
157
|
+
/**
|
|
158
|
+
* Create a localizable function for a command.
|
|
159
|
+
* This function will resolve the translation key based on the command context and the provided translation function.
|
|
160
|
+
* @param ctx Command context
|
|
161
|
+
* @param cmd Command
|
|
162
|
+
* @param translate Translation function
|
|
163
|
+
* @returns Localizable function
|
|
164
|
+
*/
|
|
165
|
+
function localizable(ctx, cmd, translate) {
|
|
166
|
+
async function localize(key, values) {
|
|
167
|
+
if (translate) return translate(key, values);
|
|
168
|
+
if (key.startsWith(BUILD_IN_PREFIX_AND_KEY_SEPARATOR)) {
|
|
169
|
+
const resKey = key.slice(BUILD_IN_PREFIX_AND_KEY_SEPARATOR.length);
|
|
170
|
+
return en_US_default[resKey] || key;
|
|
171
|
+
}
|
|
172
|
+
const namaspacedArgKey = resolveKey(ARG_PREFIX_AND_KEY_SEPARATOR, ctx);
|
|
173
|
+
if (key.startsWith(namaspacedArgKey)) {
|
|
174
|
+
let argKey = key.slice(namaspacedArgKey.length);
|
|
175
|
+
let negatable = false;
|
|
176
|
+
if (argKey.startsWith(ARG_NEGATABLE_PREFIX)) {
|
|
177
|
+
argKey = argKey.slice(ARG_NEGATABLE_PREFIX.length);
|
|
178
|
+
negatable = true;
|
|
179
|
+
}
|
|
180
|
+
const schema = ctx.args[argKey];
|
|
181
|
+
if (!schema) return argKey;
|
|
182
|
+
return negatable && schema.type === "boolean" && schema.negatable ? `${en_US_default["NEGATABLE"]} ${makeShortLongOptionPair(schema, argKey, ctx.toKebab)}` : schema.description || "";
|
|
183
|
+
}
|
|
184
|
+
if (key === resolveKey("description", ctx)) return "";
|
|
185
|
+
else if (key === resolveKey("examples", ctx)) return await resolveExamples(ctx, cmd.examples);
|
|
186
|
+
else return key;
|
|
187
|
+
}
|
|
188
|
+
return localize;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
//#endregion
|
|
192
|
+
//#region src/bombshell/bash.ts
|
|
4
193
|
function generate$3(name, exec) {
|
|
5
194
|
const nameForVar = name.replace(/[-:]/g, "_");
|
|
6
195
|
const ShellCompDirectiveError = ShellCompDirective.ShellCompDirectiveError;
|
|
@@ -32,31 +221,31 @@ __${nameForVar}_complete() {
|
|
|
32
221
|
_get_comp_words_by_ref -n "=:" cur prev words cword
|
|
33
222
|
|
|
34
223
|
local requestComp out directive
|
|
35
|
-
|
|
224
|
+
|
|
36
225
|
# Build the command to get completions
|
|
37
226
|
requestComp="${exec} complete -- \${words[@]:1}"
|
|
38
|
-
|
|
227
|
+
|
|
39
228
|
# Add an empty parameter if the last parameter is complete
|
|
40
229
|
if [[ -z "$cur" ]]; then
|
|
41
230
|
requestComp="$requestComp ''"
|
|
42
231
|
fi
|
|
43
|
-
|
|
232
|
+
|
|
44
233
|
# Get completions from the program
|
|
45
234
|
out=$(eval "$requestComp" 2>/dev/null)
|
|
46
|
-
|
|
235
|
+
|
|
47
236
|
# Extract directive if present
|
|
48
237
|
directive=0
|
|
49
238
|
if [[ "$out" == *:* ]]; then
|
|
50
239
|
directive=\${out##*:}
|
|
51
240
|
out=\${out%:*}
|
|
52
241
|
fi
|
|
53
|
-
|
|
242
|
+
|
|
54
243
|
# Process completions based on directive
|
|
55
244
|
if [[ $((directive & $ShellCompDirectiveError)) -ne 0 ]]; then
|
|
56
245
|
# Error, no completion
|
|
57
246
|
return
|
|
58
247
|
fi
|
|
59
|
-
|
|
248
|
+
|
|
60
249
|
# Apply directives
|
|
61
250
|
if [[ $((directive & $ShellCompDirectiveNoSpace)) -ne 0 ]]; then
|
|
62
251
|
compopt -o nospace
|
|
@@ -67,7 +256,7 @@ __${nameForVar}_complete() {
|
|
|
67
256
|
if [[ $((directive & $ShellCompDirectiveNoFileComp)) -ne 0 ]]; then
|
|
68
257
|
compopt +o default
|
|
69
258
|
fi
|
|
70
|
-
|
|
259
|
+
|
|
71
260
|
# Handle file extension filtering
|
|
72
261
|
if [[ $((directive & $ShellCompDirectiveFilterFileExt)) -ne 0 ]]; then
|
|
73
262
|
local filter=""
|
|
@@ -79,18 +268,18 @@ __${nameForVar}_complete() {
|
|
|
79
268
|
COMPREPLY=( $(compgen -f -X "!$filter" -- "$cur") )
|
|
80
269
|
return
|
|
81
270
|
fi
|
|
82
|
-
|
|
271
|
+
|
|
83
272
|
# Handle directory filtering
|
|
84
273
|
if [[ $((directive & $ShellCompDirectiveFilterDirs)) -ne 0 ]]; then
|
|
85
274
|
compopt -o dirnames
|
|
86
275
|
COMPREPLY=( $(compgen -d -- "$cur") )
|
|
87
276
|
return
|
|
88
277
|
fi
|
|
89
|
-
|
|
278
|
+
|
|
90
279
|
# Process completions
|
|
91
280
|
local IFS=$'\\n'
|
|
92
281
|
local tab=$(printf '\\t')
|
|
93
|
-
|
|
282
|
+
|
|
94
283
|
# Parse completions with descriptions
|
|
95
284
|
local completions=()
|
|
96
285
|
while read -r comp; do
|
|
@@ -103,7 +292,7 @@ __${nameForVar}_complete() {
|
|
|
103
292
|
completions+=("$comp")
|
|
104
293
|
fi
|
|
105
294
|
done <<< "$out"
|
|
106
|
-
|
|
295
|
+
|
|
107
296
|
# Return completions
|
|
108
297
|
COMPREPLY=( $(compgen -W "\${completions[*]}" -- "$cur") )
|
|
109
298
|
}
|
|
@@ -112,6 +301,9 @@ __${nameForVar}_complete() {
|
|
|
112
301
|
complete -F __${nameForVar}_complete ${name}
|
|
113
302
|
`;
|
|
114
303
|
}
|
|
304
|
+
|
|
305
|
+
//#endregion
|
|
306
|
+
//#region src/bombshell/fish.ts
|
|
115
307
|
function generate$2(name, exec) {
|
|
116
308
|
const nameForVar = name.replace(/[-:]/g, "_");
|
|
117
309
|
const ShellCompDirectiveError = ShellCompDirective.ShellCompDirectiveError;
|
|
@@ -142,16 +334,16 @@ function __${nameForVar}_perform_completion
|
|
|
142
334
|
|
|
143
335
|
# Extract all args except the completion flag
|
|
144
336
|
set -l args (string match -v -- "--completion=" (commandline -opc))
|
|
145
|
-
|
|
337
|
+
|
|
146
338
|
# Extract the current token being completed
|
|
147
339
|
set -l current_token (commandline -ct)
|
|
148
|
-
|
|
340
|
+
|
|
149
341
|
# Check if current token starts with a dash
|
|
150
342
|
set -l flag_prefix ""
|
|
151
343
|
if string match -q -- "-*" $current_token
|
|
152
344
|
set flag_prefix "--flag="
|
|
153
345
|
end
|
|
154
|
-
|
|
346
|
+
|
|
155
347
|
__${nameForVar}_debug "Current token: $current_token"
|
|
156
348
|
__${nameForVar}_debug "All args: $args"
|
|
157
349
|
|
|
@@ -159,7 +351,7 @@ function __${nameForVar}_perform_completion
|
|
|
159
351
|
set -l requestComp "${exec} complete -- $args"
|
|
160
352
|
__${nameForVar}_debug "Calling $requestComp"
|
|
161
353
|
set -l results (eval $requestComp 2> /dev/null)
|
|
162
|
-
|
|
354
|
+
|
|
163
355
|
# Some programs may output extra empty lines after the directive.
|
|
164
356
|
# Let's ignore them or else it will break completion.
|
|
165
357
|
# Ref: https://github.com/spf13/cobra/issues/1279
|
|
@@ -171,12 +363,12 @@ function __${nameForVar}_perform_completion
|
|
|
171
363
|
break
|
|
172
364
|
end
|
|
173
365
|
end
|
|
174
|
-
|
|
366
|
+
|
|
175
367
|
# No directive specified, use default
|
|
176
368
|
if not set -q directive_num
|
|
177
369
|
set directive_num 0
|
|
178
370
|
end
|
|
179
|
-
|
|
371
|
+
|
|
180
372
|
__${nameForVar}_debug "Directive: $directive_num"
|
|
181
373
|
|
|
182
374
|
# Process completions based on directive
|
|
@@ -211,14 +403,14 @@ function __${nameForVar}_perform_completion
|
|
|
211
403
|
end
|
|
212
404
|
end
|
|
213
405
|
__${nameForVar}_debug "File extensions: $file_extensions"
|
|
214
|
-
|
|
406
|
+
|
|
215
407
|
# Use the file extensions as completions
|
|
216
408
|
set -l completions
|
|
217
409
|
for ext in $file_extensions
|
|
218
410
|
# Get all files matching the extension
|
|
219
411
|
set -a completions (string replace -r '^.*/' '' -- $ext)
|
|
220
412
|
end
|
|
221
|
-
|
|
413
|
+
|
|
222
414
|
for item in $completions
|
|
223
415
|
echo -e "$item\t"
|
|
224
416
|
end
|
|
@@ -234,7 +426,7 @@ function __${nameForVar}_perform_completion
|
|
|
234
426
|
set -a dirs "$item/"
|
|
235
427
|
end
|
|
236
428
|
end
|
|
237
|
-
|
|
429
|
+
|
|
238
430
|
for item in $dirs
|
|
239
431
|
echo -e "$item\t"
|
|
240
432
|
end
|
|
@@ -249,7 +441,7 @@ function __${nameForVar}_perform_completion
|
|
|
249
441
|
set -l completion_parts (string split \t -- "$item")
|
|
250
442
|
set -l comp $completion_parts[1]
|
|
251
443
|
set -l desc $completion_parts[2]
|
|
252
|
-
|
|
444
|
+
|
|
253
445
|
# Add the completion and description
|
|
254
446
|
echo -e "$comp\t$desc"
|
|
255
447
|
else
|
|
@@ -258,12 +450,12 @@ function __${nameForVar}_perform_completion
|
|
|
258
450
|
end
|
|
259
451
|
end
|
|
260
452
|
end
|
|
261
|
-
|
|
453
|
+
|
|
262
454
|
# If directive contains NoSpace, tell fish not to add a space after completion
|
|
263
455
|
if test (math "$directive_num & $ShellCompDirectiveNoSpace") -ne 0
|
|
264
456
|
return 2
|
|
265
457
|
end
|
|
266
|
-
|
|
458
|
+
|
|
267
459
|
return 0
|
|
268
460
|
end
|
|
269
461
|
|
|
@@ -271,7 +463,10 @@ end
|
|
|
271
463
|
complete -c ${name} -f -a "(eval __${nameForVar}_perform_completion)"
|
|
272
464
|
`;
|
|
273
465
|
}
|
|
274
|
-
|
|
466
|
+
|
|
467
|
+
//#endregion
|
|
468
|
+
//#region src/bombshell/powershell.ts
|
|
469
|
+
function generate$1(name, exec, _includeDesc = false) {
|
|
275
470
|
const nameForVar = name.replace(/[-:]/g, "_");
|
|
276
471
|
const ShellCompDirectiveError = ShellCompDirective.ShellCompDirectiveError;
|
|
277
472
|
const ShellCompDirectiveNoSpace = ShellCompDirective.ShellCompDirectiveNoSpace;
|
|
@@ -526,199 +721,9 @@ function generate$1(name, exec, includeDesc = false) {
|
|
|
526
721
|
Register-ArgumentCompleter -CommandName '${name}' -ScriptBlock $__${nameForVar}CompleterBlock
|
|
527
722
|
`;
|
|
528
723
|
}
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
ShellCompDirectiveNoFileComp: 4,
|
|
533
|
-
ShellCompDirectiveFilterFileExt: 8,
|
|
534
|
-
ShellCompDirectiveFilterDirs: 16,
|
|
535
|
-
ShellCompDirectiveKeepOrder: 32,
|
|
536
|
-
shellCompDirectiveMaxValue: 64,
|
|
537
|
-
ShellCompDirectiveDefault: 0
|
|
538
|
-
};
|
|
539
|
-
var Completion = class {
|
|
540
|
-
commands = new Map();
|
|
541
|
-
completions = [];
|
|
542
|
-
directive = ShellCompDirective.ShellCompDirectiveDefault;
|
|
543
|
-
addCommand(name, description, args, handler, parent) {
|
|
544
|
-
const key = parent ? `${parent} ${name}` : name;
|
|
545
|
-
this.commands.set(key, {
|
|
546
|
-
name: key,
|
|
547
|
-
description,
|
|
548
|
-
args,
|
|
549
|
-
handler,
|
|
550
|
-
options: new Map(),
|
|
551
|
-
parent: parent ? this.commands.get(parent) : void 0
|
|
552
|
-
});
|
|
553
|
-
return key;
|
|
554
|
-
}
|
|
555
|
-
addOption(command, option, description, handler, alias) {
|
|
556
|
-
const cmd = this.commands.get(command);
|
|
557
|
-
if (!cmd) throw new Error(`Command ${command} not found.`);
|
|
558
|
-
cmd.options.set(option, {
|
|
559
|
-
description,
|
|
560
|
-
handler,
|
|
561
|
-
alias
|
|
562
|
-
});
|
|
563
|
-
return option;
|
|
564
|
-
}
|
|
565
|
-
stripOptions(args) {
|
|
566
|
-
const parts = [];
|
|
567
|
-
let option = false;
|
|
568
|
-
for (const k of args) {
|
|
569
|
-
if (k.startsWith("-")) {
|
|
570
|
-
option = true;
|
|
571
|
-
continue;
|
|
572
|
-
}
|
|
573
|
-
if (option) {
|
|
574
|
-
option = false;
|
|
575
|
-
continue;
|
|
576
|
-
}
|
|
577
|
-
parts.push(k);
|
|
578
|
-
}
|
|
579
|
-
return parts;
|
|
580
|
-
}
|
|
581
|
-
matchCommand(args) {
|
|
582
|
-
args = this.stripOptions(args);
|
|
583
|
-
const parts = [];
|
|
584
|
-
let remaining = [];
|
|
585
|
-
let matched = this.commands.get("");
|
|
586
|
-
for (let i = 0; i < args.length; i++) {
|
|
587
|
-
const k = args[i];
|
|
588
|
-
parts.push(k);
|
|
589
|
-
const potential = this.commands.get(parts.join(" "));
|
|
590
|
-
if (potential) matched = potential;
|
|
591
|
-
else {
|
|
592
|
-
remaining = args.slice(i, args.length);
|
|
593
|
-
break;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
596
|
-
return [matched, remaining];
|
|
597
|
-
}
|
|
598
|
-
async parse(args) {
|
|
599
|
-
const endsWithSpace = args[args.length - 1] === "";
|
|
600
|
-
if (endsWithSpace) args.pop();
|
|
601
|
-
let toComplete = args[args.length - 1] || "";
|
|
602
|
-
const previousArgs = args.slice(0, -1);
|
|
603
|
-
if (endsWithSpace) {
|
|
604
|
-
previousArgs.push(toComplete);
|
|
605
|
-
toComplete = "";
|
|
606
|
-
}
|
|
607
|
-
const [matchedCommand] = this.matchCommand(previousArgs);
|
|
608
|
-
const lastPrevArg = previousArgs[previousArgs.length - 1];
|
|
609
|
-
if (this.shouldCompleteFlags(lastPrevArg, toComplete, endsWithSpace)) await this.handleFlagCompletion(matchedCommand, previousArgs, toComplete, endsWithSpace, lastPrevArg);
|
|
610
|
-
else {
|
|
611
|
-
if (this.shouldCompleteCommands(toComplete, endsWithSpace)) await this.handleCommandCompletion(previousArgs, toComplete);
|
|
612
|
-
if (matchedCommand && matchedCommand.args.length > 0) await this.handlePositionalCompletion(matchedCommand, previousArgs, toComplete, endsWithSpace);
|
|
613
|
-
}
|
|
614
|
-
this.complete(toComplete);
|
|
615
|
-
}
|
|
616
|
-
complete(toComplete) {
|
|
617
|
-
this.directive = ShellCompDirective.ShellCompDirectiveNoFileComp;
|
|
618
|
-
const seen = new Set();
|
|
619
|
-
this.completions.filter((comp) => {
|
|
620
|
-
if (seen.has(comp.value)) return false;
|
|
621
|
-
seen.add(comp.value);
|
|
622
|
-
return true;
|
|
623
|
-
}).filter((comp) => comp.value.startsWith(toComplete)).forEach((comp) => console.log(`${comp.value}\t${comp.description ?? ""}`));
|
|
624
|
-
console.log(`:${this.directive}`);
|
|
625
|
-
}
|
|
626
|
-
shouldCompleteFlags(lastPrevArg, toComplete, endsWithSpace) {
|
|
627
|
-
return lastPrevArg?.startsWith("--") || lastPrevArg?.startsWith("-") || toComplete.startsWith("--") || toComplete.startsWith("-");
|
|
628
|
-
}
|
|
629
|
-
shouldCompleteCommands(toComplete, endsWithSpace) {
|
|
630
|
-
return !toComplete.startsWith("-");
|
|
631
|
-
}
|
|
632
|
-
async handleFlagCompletion(command, previousArgs, toComplete, endsWithSpace, lastPrevArg) {
|
|
633
|
-
let flagName;
|
|
634
|
-
let valueToComplete = toComplete;
|
|
635
|
-
if (toComplete.includes("=")) {
|
|
636
|
-
const parts = toComplete.split("=");
|
|
637
|
-
flagName = parts[0];
|
|
638
|
-
valueToComplete = parts[1] || "";
|
|
639
|
-
} else if (lastPrevArg?.startsWith("-")) flagName = lastPrevArg;
|
|
640
|
-
if (flagName) {
|
|
641
|
-
let option = command.options.get(flagName);
|
|
642
|
-
if (!option) {
|
|
643
|
-
for (const [name, opt] of command.options) if (opt.alias && `-${opt.alias}` === flagName) {
|
|
644
|
-
option = opt;
|
|
645
|
-
flagName = name;
|
|
646
|
-
break;
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
if (option) {
|
|
650
|
-
const suggestions = await option.handler(previousArgs, valueToComplete, endsWithSpace);
|
|
651
|
-
if (toComplete.includes("=")) this.completions = suggestions.map((suggestion) => ({
|
|
652
|
-
value: `${flagName}=${suggestion.value}`,
|
|
653
|
-
description: suggestion.description
|
|
654
|
-
}));
|
|
655
|
-
else this.completions.push(...suggestions);
|
|
656
|
-
}
|
|
657
|
-
return;
|
|
658
|
-
}
|
|
659
|
-
if (toComplete.startsWith("-")) {
|
|
660
|
-
const isShortFlag = toComplete.startsWith("-") && !toComplete.startsWith("--");
|
|
661
|
-
for (const [name, option] of command.options) if (isShortFlag) {
|
|
662
|
-
if (option.alias && `-${option.alias}`.startsWith(toComplete)) this.completions.push({
|
|
663
|
-
value: `-${option.alias}`,
|
|
664
|
-
description: option.description
|
|
665
|
-
});
|
|
666
|
-
} else if (name.startsWith(toComplete)) this.completions.push({
|
|
667
|
-
value: name,
|
|
668
|
-
description: option.description
|
|
669
|
-
});
|
|
670
|
-
}
|
|
671
|
-
}
|
|
672
|
-
async handleCommandCompletion(previousArgs, toComplete) {
|
|
673
|
-
const commandParts = [...previousArgs].filter(Boolean);
|
|
674
|
-
for (const [k, command] of this.commands) {
|
|
675
|
-
if (k === "") continue;
|
|
676
|
-
const parts = k.split(" ");
|
|
677
|
-
let match = true;
|
|
678
|
-
let i = 0;
|
|
679
|
-
while (i < commandParts.length) {
|
|
680
|
-
if (parts[i] !== commandParts[i]) {
|
|
681
|
-
match = false;
|
|
682
|
-
break;
|
|
683
|
-
}
|
|
684
|
-
i++;
|
|
685
|
-
}
|
|
686
|
-
if (match && parts[i]?.startsWith(toComplete)) this.completions.push({
|
|
687
|
-
value: parts[i],
|
|
688
|
-
description: command.description
|
|
689
|
-
});
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
async handlePositionalCompletion(command, previousArgs, toComplete, endsWithSpace) {
|
|
693
|
-
const suggestions = await command.handler(previousArgs, toComplete, endsWithSpace);
|
|
694
|
-
this.completions.push(...suggestions);
|
|
695
|
-
}
|
|
696
|
-
};
|
|
697
|
-
function script(shell, name, x) {
|
|
698
|
-
switch (shell) {
|
|
699
|
-
case "zsh": {
|
|
700
|
-
const script$1 = generate(name, x);
|
|
701
|
-
console.log(script$1);
|
|
702
|
-
break;
|
|
703
|
-
}
|
|
704
|
-
case "bash": {
|
|
705
|
-
const script$1 = generate$3(name, x);
|
|
706
|
-
console.log(script$1);
|
|
707
|
-
break;
|
|
708
|
-
}
|
|
709
|
-
case "fish": {
|
|
710
|
-
const script$1 = generate$2(name, x);
|
|
711
|
-
console.log(script$1);
|
|
712
|
-
break;
|
|
713
|
-
}
|
|
714
|
-
case "powershell": {
|
|
715
|
-
const script$1 = generate$1(name, x);
|
|
716
|
-
console.log(script$1);
|
|
717
|
-
break;
|
|
718
|
-
}
|
|
719
|
-
default: throw new Error(`Unsupported shell: ${shell}`);
|
|
720
|
-
}
|
|
721
|
-
}
|
|
724
|
+
|
|
725
|
+
//#endregion
|
|
726
|
+
//#region src/bombshell/zsh.ts
|
|
722
727
|
function generate(name, exec) {
|
|
723
728
|
return `#compdef ${name}
|
|
724
729
|
compdef _${name} ${name}
|
|
@@ -939,72 +944,199 @@ fi
|
|
|
939
944
|
}
|
|
940
945
|
|
|
941
946
|
//#endregion
|
|
942
|
-
//#region
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
947
|
+
//#region src/bombshell/index.ts
|
|
948
|
+
const ShellCompDirective = {
|
|
949
|
+
ShellCompDirectiveError: Math.trunc(1),
|
|
950
|
+
ShellCompDirectiveNoSpace: 2,
|
|
951
|
+
ShellCompDirectiveNoFileComp: 4,
|
|
952
|
+
ShellCompDirectiveFilterFileExt: 8,
|
|
953
|
+
ShellCompDirectiveFilterDirs: 16,
|
|
954
|
+
ShellCompDirectiveKeepOrder: 32,
|
|
955
|
+
shellCompDirectiveMaxValue: 64,
|
|
956
|
+
ShellCompDirectiveDefault: 0
|
|
957
|
+
};
|
|
958
|
+
var Completion = class {
|
|
959
|
+
commands = new Map();
|
|
960
|
+
completions = [];
|
|
961
|
+
directive = ShellCompDirective.ShellCompDirectiveDefault;
|
|
962
|
+
addCommand(name, description, args, handler, parent) {
|
|
963
|
+
const key = parent ? `${parent} ${name}` : name;
|
|
964
|
+
this.commands.set(key, {
|
|
965
|
+
name: key,
|
|
966
|
+
description,
|
|
967
|
+
args,
|
|
968
|
+
handler,
|
|
969
|
+
options: new Map(),
|
|
970
|
+
parent: parent ? this.commands.get(parent) : void 0
|
|
971
|
+
});
|
|
972
|
+
return key;
|
|
973
|
+
}
|
|
974
|
+
addOption(command, option, description, handler, alias) {
|
|
975
|
+
const cmd = this.commands.get(command);
|
|
976
|
+
if (!cmd) throw new Error(`Command ${command} not found.`);
|
|
977
|
+
cmd.options.set(option, {
|
|
978
|
+
description,
|
|
979
|
+
handler,
|
|
980
|
+
alias
|
|
981
|
+
});
|
|
982
|
+
return option;
|
|
983
|
+
}
|
|
984
|
+
stripOptions(args) {
|
|
985
|
+
const parts = [];
|
|
986
|
+
let option = false;
|
|
987
|
+
for (const k of args) {
|
|
988
|
+
if (k.startsWith("-")) {
|
|
989
|
+
option = true;
|
|
990
|
+
continue;
|
|
991
|
+
}
|
|
992
|
+
if (option) {
|
|
993
|
+
option = false;
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
parts.push(k);
|
|
973
997
|
}
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
998
|
+
return parts;
|
|
999
|
+
}
|
|
1000
|
+
matchCommand(args) {
|
|
1001
|
+
args = this.stripOptions(args);
|
|
1002
|
+
const parts = [];
|
|
1003
|
+
let remaining = [];
|
|
1004
|
+
let matched = this.commands.get("");
|
|
1005
|
+
for (let i = 0; i < args.length; i++) {
|
|
1006
|
+
const k = args[i];
|
|
1007
|
+
parts.push(k);
|
|
1008
|
+
const potential = this.commands.get(parts.join(" "));
|
|
1009
|
+
if (potential) matched = potential;
|
|
1010
|
+
else {
|
|
1011
|
+
remaining = args.slice(i);
|
|
1012
|
+
break;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
return [matched, remaining];
|
|
1016
|
+
}
|
|
1017
|
+
async parse(args) {
|
|
1018
|
+
const endsWithSpace = args.at(-1) === "";
|
|
1019
|
+
if (endsWithSpace) args.pop();
|
|
1020
|
+
let toComplete = args.at(-1) || "";
|
|
1021
|
+
const previousArgs = args.slice(0, -1);
|
|
1022
|
+
if (endsWithSpace) {
|
|
1023
|
+
previousArgs.push(toComplete);
|
|
1024
|
+
toComplete = "";
|
|
1025
|
+
}
|
|
1026
|
+
const [matchedCommand] = this.matchCommand(previousArgs);
|
|
1027
|
+
const lastPrevArg = previousArgs.at(-1);
|
|
1028
|
+
if (this.shouldCompleteFlags(lastPrevArg, toComplete, endsWithSpace)) await this.handleFlagCompletion(matchedCommand, previousArgs, toComplete, endsWithSpace, lastPrevArg);
|
|
1029
|
+
else {
|
|
1030
|
+
if (this.shouldCompleteCommands(toComplete, endsWithSpace)) await this.handleCommandCompletion(previousArgs, toComplete);
|
|
1031
|
+
if (matchedCommand && matchedCommand.args.length > 0) await this.handlePositionalCompletion(matchedCommand, previousArgs, toComplete, endsWithSpace);
|
|
1032
|
+
}
|
|
1033
|
+
this.complete(toComplete);
|
|
1034
|
+
}
|
|
1035
|
+
complete(toComplete) {
|
|
1036
|
+
this.directive = ShellCompDirective.ShellCompDirectiveNoFileComp;
|
|
1037
|
+
const seen = new Set();
|
|
1038
|
+
for (const comp of this.completions.filter((comp$1) => {
|
|
1039
|
+
if (seen.has(comp$1.value)) return false;
|
|
1040
|
+
seen.add(comp$1.value);
|
|
1041
|
+
return true;
|
|
1042
|
+
}).filter((comp$1) => comp$1.value.startsWith(toComplete))) console.log(`${comp.value}\t${comp.description ?? ""}`);
|
|
1043
|
+
console.log(`:${this.directive}`);
|
|
1044
|
+
}
|
|
1045
|
+
shouldCompleteFlags(lastPrevArg, toComplete, _endsWithSpace) {
|
|
1046
|
+
return lastPrevArg?.startsWith("--") || lastPrevArg?.startsWith("-") || toComplete.startsWith("--") || toComplete.startsWith("-");
|
|
1047
|
+
}
|
|
1048
|
+
shouldCompleteCommands(toComplete, _endsWithSpace) {
|
|
1049
|
+
return !toComplete.startsWith("-");
|
|
1050
|
+
}
|
|
1051
|
+
async handleFlagCompletion(command, previousArgs, toComplete, endsWithSpace, lastPrevArg) {
|
|
1052
|
+
let flagName;
|
|
1053
|
+
let valueToComplete = toComplete;
|
|
1054
|
+
if (toComplete.includes("=")) {
|
|
1055
|
+
const parts = toComplete.split("=");
|
|
1056
|
+
flagName = parts[0];
|
|
1057
|
+
valueToComplete = parts[1] || "";
|
|
1058
|
+
} else if (lastPrevArg?.startsWith("-")) flagName = lastPrevArg;
|
|
1059
|
+
if (flagName) {
|
|
1060
|
+
let option = command.options.get(flagName);
|
|
1061
|
+
if (!option) {
|
|
1062
|
+
for (const [name, opt] of command.options) if (opt.alias && `-${opt.alias}` === flagName) {
|
|
1063
|
+
option = opt;
|
|
1064
|
+
flagName = name;
|
|
1065
|
+
break;
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
if (option) {
|
|
1069
|
+
const suggestions = await option.handler(previousArgs, valueToComplete, endsWithSpace);
|
|
1070
|
+
if (toComplete.includes("=")) this.completions = suggestions.map((suggestion) => ({
|
|
1071
|
+
value: `${flagName}=${suggestion.value}`,
|
|
1072
|
+
description: suggestion.description
|
|
1073
|
+
}));
|
|
1074
|
+
else this.completions.push(...suggestions);
|
|
1075
|
+
}
|
|
1076
|
+
return;
|
|
1077
|
+
}
|
|
1078
|
+
if (toComplete.startsWith("-")) {
|
|
1079
|
+
const isShortFlag = toComplete.startsWith("-") && !toComplete.startsWith("--");
|
|
1080
|
+
for (const [name, option] of command.options) if (isShortFlag) {
|
|
1081
|
+
if (option.alias && `-${option.alias}`.startsWith(toComplete)) this.completions.push({
|
|
1082
|
+
value: `-${option.alias}`,
|
|
1083
|
+
description: option.description
|
|
1084
|
+
});
|
|
1085
|
+
} else if (name.startsWith(toComplete)) this.completions.push({
|
|
1086
|
+
value: name,
|
|
1087
|
+
description: option.description
|
|
1088
|
+
});
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async handleCommandCompletion(previousArgs, toComplete) {
|
|
1092
|
+
const commandParts = [...previousArgs].filter(Boolean);
|
|
1093
|
+
for (const [k, command] of this.commands) {
|
|
1094
|
+
if (k === "") continue;
|
|
1095
|
+
const parts = k.split(" ");
|
|
1096
|
+
let match = true;
|
|
1097
|
+
let i = 0;
|
|
1098
|
+
while (i < commandParts.length) {
|
|
1099
|
+
if (parts[i] !== commandParts[i]) {
|
|
1100
|
+
match = false;
|
|
1101
|
+
break;
|
|
1102
|
+
}
|
|
1103
|
+
i++;
|
|
1104
|
+
}
|
|
1105
|
+
if (match && parts[i]?.startsWith(toComplete)) this.completions.push({
|
|
1106
|
+
value: parts[i],
|
|
1107
|
+
description: command.description
|
|
1108
|
+
});
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
async handlePositionalCompletion(command, previousArgs, toComplete, endsWithSpace) {
|
|
1112
|
+
const suggestions = await command.handler(previousArgs, toComplete, endsWithSpace);
|
|
1113
|
+
this.completions.push(...suggestions);
|
|
1114
|
+
}
|
|
1115
|
+
};
|
|
1116
|
+
function script(shell, name, x) {
|
|
1117
|
+
switch (shell) {
|
|
1118
|
+
case "zsh": {
|
|
1119
|
+
const script$1 = generate(name, x);
|
|
1120
|
+
console.log(script$1);
|
|
1121
|
+
break;
|
|
1122
|
+
}
|
|
1123
|
+
case "bash": {
|
|
1124
|
+
const script$1 = generate$3(name, x);
|
|
1125
|
+
console.log(script$1);
|
|
1126
|
+
break;
|
|
1127
|
+
}
|
|
1128
|
+
case "fish": {
|
|
1129
|
+
const script$1 = generate$2(name, x);
|
|
1130
|
+
console.log(script$1);
|
|
1131
|
+
break;
|
|
1132
|
+
}
|
|
1133
|
+
case "powershell": {
|
|
1134
|
+
const script$1 = generate$1(name, x);
|
|
1135
|
+
console.log(script$1);
|
|
1136
|
+
break;
|
|
1137
|
+
}
|
|
1138
|
+
default: throw new Error(`Unsupported shell: ${shell}`);
|
|
987
1139
|
}
|
|
988
|
-
return Object.freeze(obj);
|
|
989
|
-
}
|
|
990
|
-
|
|
991
|
-
//#endregion
|
|
992
|
-
//#region ../shared/src/constants.ts
|
|
993
|
-
/**
|
|
994
|
-
* @author kazuya kawaguchi (a.k.a. kazupon)
|
|
995
|
-
* @license MIT
|
|
996
|
-
*/
|
|
997
|
-
const BUILT_IN_PREFIX = "_";
|
|
998
|
-
const PLUGIN_PREFIX = "g";
|
|
999
|
-
const ARG_PREFIX = "arg";
|
|
1000
|
-
const BUILT_IN_KEY_SEPARATOR = ":";
|
|
1001
|
-
const BUILD_IN_PREFIX_AND_KEY_SEPARATOR = `${BUILT_IN_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
|
|
1002
|
-
const ARG_PREFIX_AND_KEY_SEPARATOR = `${ARG_PREFIX}${BUILT_IN_KEY_SEPARATOR}`;
|
|
1003
|
-
|
|
1004
|
-
//#endregion
|
|
1005
|
-
//#region ../shared/src/utils.ts
|
|
1006
|
-
function namespacedId(id) {
|
|
1007
|
-
return `${PLUGIN_PREFIX}${BUILT_IN_KEY_SEPARATOR}${id}`;
|
|
1008
1140
|
}
|
|
1009
1141
|
|
|
1010
1142
|
//#endregion
|
|
@@ -1016,10 +1148,27 @@ const pluginId = namespacedId("completion");
|
|
|
1016
1148
|
|
|
1017
1149
|
//#endregion
|
|
1018
1150
|
//#region src/utils.ts
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1151
|
+
async function createCommandContext$1(cmd, id, i18n) {
|
|
1152
|
+
const extensions = Object.create(null);
|
|
1153
|
+
if (i18n) extensions[id] = {
|
|
1154
|
+
key: Symbol(id),
|
|
1155
|
+
factory: () => i18n
|
|
1156
|
+
};
|
|
1157
|
+
return await createCommandContext({
|
|
1158
|
+
args: cmd.args || Object.create(null),
|
|
1159
|
+
values: Object.create(null),
|
|
1160
|
+
positionals: [],
|
|
1161
|
+
rest: [],
|
|
1162
|
+
argv: [],
|
|
1163
|
+
explicit: Object.create(null),
|
|
1164
|
+
tokens: [],
|
|
1165
|
+
omitted: false,
|
|
1166
|
+
callMode: cmd.entry ? "entry" : "subCommand",
|
|
1167
|
+
command: cmd,
|
|
1168
|
+
extensions,
|
|
1169
|
+
cliOptions: CLI_OPTIONS_DEFAULT
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
1023
1172
|
function detectRuntime() {
|
|
1024
1173
|
if (globalThis.process !== void 0 && globalThis.process.release?.name === "node") return "node";
|
|
1025
1174
|
if (globalThis.Deno !== void 0) return "deno";
|
|
@@ -1052,6 +1201,7 @@ const TERMINATOR = "--";
|
|
|
1052
1201
|
const NOOP_HANDLER = () => {
|
|
1053
1202
|
return [];
|
|
1054
1203
|
};
|
|
1204
|
+
const i18nPluginId = namespacedId("i18n");
|
|
1055
1205
|
/**
|
|
1056
1206
|
* completion plugin for gunshi
|
|
1057
1207
|
*/
|
|
@@ -1061,6 +1211,10 @@ function completion(options = {}) {
|
|
|
1061
1211
|
return plugin({
|
|
1062
1212
|
id: pluginId,
|
|
1063
1213
|
name: "completion",
|
|
1214
|
+
dependencies: [{
|
|
1215
|
+
id: i18nPluginId,
|
|
1216
|
+
optional: true
|
|
1217
|
+
}],
|
|
1064
1218
|
async setup(ctx) {
|
|
1065
1219
|
/**
|
|
1066
1220
|
* add command for completion script generation
|
|
@@ -1069,6 +1223,7 @@ function completion(options = {}) {
|
|
|
1069
1223
|
ctx.addCommand(completeName, {
|
|
1070
1224
|
name: completeName,
|
|
1071
1225
|
description: "Generate shell completion script",
|
|
1226
|
+
rendering: { header: null },
|
|
1072
1227
|
run: async (cmdCtx) => {
|
|
1073
1228
|
if (!cmdCtx.env.name) throw new Error("your cli name is not defined.");
|
|
1074
1229
|
let shell = cmdCtx._[1];
|
|
@@ -1079,46 +1234,63 @@ function completion(options = {}) {
|
|
|
1079
1234
|
} else script(shell, cmdCtx.env.name, quoteExec());
|
|
1080
1235
|
}
|
|
1081
1236
|
});
|
|
1082
|
-
/**
|
|
1083
|
-
* disable header renderer
|
|
1084
|
-
*/
|
|
1085
|
-
ctx.decorateHeaderRenderer(async (_baseRenderer, _cmdCtx) => "");
|
|
1086
1237
|
},
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
},
|
|
1090
|
-
onExtension: async (ctx, _cmd) => {
|
|
1238
|
+
onExtension: async (ctx, cmd) => {
|
|
1239
|
+
const i18n = ctx.extensions?.[i18nPluginId];
|
|
1091
1240
|
const subCommands = ctx.env.subCommands;
|
|
1092
|
-
const entry = [...subCommands].map(([_, cmd]) => cmd).find((cmd) => cmd.entry);
|
|
1241
|
+
const entry = [...subCommands].map(([_, cmd$1]) => cmd$1).find((cmd$1) => cmd$1.entry);
|
|
1093
1242
|
if (!entry) throw new Error("entry command not found.");
|
|
1243
|
+
const entryCtx = await createCommandContext$1(entry, i18nPluginId, i18n);
|
|
1244
|
+
if (i18n) {
|
|
1245
|
+
const ret = await i18n.loadResource(i18n.locale, entryCtx, entry);
|
|
1246
|
+
if (!ret) console.warn(`Failed to load i18n resources for command: ${entry.name} (${i18n.locale})`);
|
|
1247
|
+
}
|
|
1248
|
+
const localizeDescription = localizable(entryCtx, cmd, i18n ? i18n.translate : void 0);
|
|
1094
1249
|
const isPositional = hasPositional(await resolveLazyCommand(entry));
|
|
1095
1250
|
const root = "";
|
|
1096
|
-
completion$1.addCommand(root, entry.description || "", isPositional ? [false] : [], NOOP_HANDLER);
|
|
1251
|
+
completion$1.addCommand(root, await localizeDescription(resolveKey("description", entryCtx)) || entry.description || "", isPositional ? [false] : [], NOOP_HANDLER);
|
|
1097
1252
|
const args = entry.args || Object.create(null);
|
|
1098
1253
|
for (const [key, schema] of Object.entries(args)) {
|
|
1099
1254
|
if (schema.type === "positional") continue;
|
|
1100
|
-
completion$1.addOption(root, `--${key}`, schema.description || "", config.entry?.args?.[key]?.handler || NOOP_HANDLER, schema.short);
|
|
1255
|
+
completion$1.addOption(root, `--${key}`, await localizeDescription(resolveArgKey(key, entryCtx)) || schema.description || "", toBombshellCompletionHandler(config.entry?.args?.[key]?.handler || NOOP_HANDLER, i18n ? toLocale(i18n.locale) : void 0), schema.short);
|
|
1101
1256
|
}
|
|
1102
|
-
await handleSubCommands(completion$1, subCommands, config.subCommands);
|
|
1257
|
+
await handleSubCommands(completion$1, subCommands, config.subCommands, i18nPluginId, i18n);
|
|
1103
1258
|
}
|
|
1104
1259
|
});
|
|
1105
1260
|
}
|
|
1106
|
-
async function handleSubCommands(completion$1, subCommands, configs = {}) {
|
|
1261
|
+
async function handleSubCommands(completion$1, subCommands, configs = {}, i18nPluginId$1, i18n) {
|
|
1107
1262
|
for (const [name, cmd] of subCommands) {
|
|
1108
1263
|
if (cmd.internal || cmd.entry || name === "complete") continue;
|
|
1109
1264
|
const resolvedCmd = await resolveLazyCommand(cmd);
|
|
1265
|
+
const ctx = await createCommandContext$1(resolvedCmd, i18nPluginId$1, i18n);
|
|
1266
|
+
if (i18n) {
|
|
1267
|
+
const ret = await i18n.loadResource(i18n.locale, ctx, resolvedCmd);
|
|
1268
|
+
if (!ret) console.warn(`Failed to load i18n resources for command: ${name} (${i18n.locale})`);
|
|
1269
|
+
}
|
|
1270
|
+
const localizeDescription = localizable(ctx, resolvedCmd, i18n ? i18n.translate : void 0);
|
|
1110
1271
|
const isPositional = hasPositional(resolvedCmd);
|
|
1111
|
-
const commandName = completion$1.addCommand(name, resolvedCmd.description || "", isPositional ? [false] : [], configs?.[name]?.handler || NOOP_HANDLER);
|
|
1272
|
+
const commandName = completion$1.addCommand(name, await localizeDescription(resolveKey("description", ctx)) || resolvedCmd.description || "", isPositional ? [false] : [], toBombshellCompletionHandler(configs?.[name]?.handler || NOOP_HANDLER, i18n ? toLocale(i18n.locale) : void 0));
|
|
1112
1273
|
const args = resolvedCmd.args || Object.create(null);
|
|
1113
1274
|
for (const [key, schema] of Object.entries(args)) {
|
|
1114
1275
|
if (schema.type === "positional") continue;
|
|
1115
|
-
completion$1.addOption(commandName, `--${key}`, schema.description || "", configs[commandName]?.args?.[key]?.handler || NOOP_HANDLER, schema.short);
|
|
1276
|
+
completion$1.addOption(commandName, `--${key}`, await localizeDescription(resolveArgKey(key, ctx)) || schema.description || "", toBombshellCompletionHandler(configs[commandName]?.args?.[key]?.handler || NOOP_HANDLER, i18n ? toLocale(i18n.locale) : void 0), schema.short);
|
|
1116
1277
|
}
|
|
1117
1278
|
}
|
|
1118
1279
|
}
|
|
1119
1280
|
function hasPositional(cmd) {
|
|
1120
1281
|
return cmd.args && Object.values(cmd.args).some((arg) => arg.type === "positional");
|
|
1121
1282
|
}
|
|
1283
|
+
function toLocale(locale) {
|
|
1284
|
+
return locale instanceof Intl.Locale ? locale : new Intl.Locale(locale);
|
|
1285
|
+
}
|
|
1286
|
+
function toBombshellCompletionHandler(handler, locale) {
|
|
1287
|
+
return (previousArgs, toComplete, endWithSpace) => handler({
|
|
1288
|
+
previousArgs,
|
|
1289
|
+
toComplete,
|
|
1290
|
+
endWithSpace,
|
|
1291
|
+
locale
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1122
1294
|
|
|
1123
1295
|
//#endregion
|
|
1124
1296
|
export { completion as default, pluginId };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gunshi/plugin-completion",
|
|
3
3
|
"description": "completion plugin for gunshi",
|
|
4
|
-
"version": "0.27.0-alpha.
|
|
4
|
+
"version": "0.27.0-alpha.9",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "kazuya kawaguchi",
|
|
7
7
|
"email": "kawakazu80@gmail.com"
|
|
@@ -52,17 +52,21 @@
|
|
|
52
52
|
}
|
|
53
53
|
},
|
|
54
54
|
"dependencies": {
|
|
55
|
-
"@
|
|
56
|
-
|
|
55
|
+
"@gunshi/plugin": "0.27.0-alpha.9"
|
|
56
|
+
},
|
|
57
|
+
"peerDependencies": {
|
|
58
|
+
"@gunshi/plugin-i18n": "0.27.0-alpha.9"
|
|
57
59
|
},
|
|
58
60
|
"devDependencies": {
|
|
59
|
-
"@types/node": "^22.16.
|
|
60
|
-
"deno": "^2.4.
|
|
61
|
+
"@types/node": "^22.16.5",
|
|
62
|
+
"deno": "^2.4.2",
|
|
61
63
|
"jsr": "^0.13.5",
|
|
62
64
|
"jsr-exports-lint": "^0.4.1",
|
|
63
65
|
"publint": "^0.3.12",
|
|
64
|
-
"
|
|
65
|
-
"
|
|
66
|
+
"rollup-plugin-license": "^3.6.0",
|
|
67
|
+
"tsdown": "^0.13.0",
|
|
68
|
+
"@gunshi/plugin-i18n": "0.27.0-alpha.9",
|
|
69
|
+
"@gunshi/shared": "0.27.0-alpha.9"
|
|
66
70
|
},
|
|
67
71
|
"scripts": {
|
|
68
72
|
"build": "tsdown",
|