@clerc/plugin-completions 0.44.0 → 1.0.0-beta.2

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.
Files changed (3) hide show
  1. package/dist/index.d.ts +10 -5
  2. package/dist/index.js +126 -129
  3. package/package.json +11 -10
package/dist/index.d.ts CHANGED
@@ -1,8 +1,13 @@
1
- import * as _clerc_core from '@clerc/core';
1
+ import { Plugin } from "@clerc/core";
2
2
 
3
+ //#region src/index.d.ts
3
4
  interface CompletionsPluginOptions {
4
- command?: boolean;
5
+ /**
6
+ * Whether to register the `completions install` and `completions uninstall` commands.
7
+ * @default true
8
+ */
9
+ managementCommands?: boolean;
5
10
  }
6
- declare const completionsPlugin: (options?: CompletionsPluginOptions) => _clerc_core.Plugin<_clerc_core.Clerc<{}, {}>, _clerc_core.Clerc<{}, {}>>;
7
-
8
- export { CompletionsPluginOptions, completionsPlugin };
11
+ declare const completionsPlugin: (options?: CompletionsPluginOptions) => Plugin;
12
+ //#endregion
13
+ export { CompletionsPluginOptions, completionsPlugin };
package/dist/index.js CHANGED
@@ -1,134 +1,131 @@
1
- import { definePlugin } from '@clerc/core';
2
- import { gracefulFlagName, kebabCase } from '@clerc/utils';
1
+ import { definePlugin, resolveCommand } from "@clerc/core";
2
+ import tabtab, { getShellFromEnv } from "@pnpm/tabtab";
3
+ import { formatFlagName, toArray } from "@clerc/utils";
3
4
 
4
- const generateCommandCompletion = (name) => `
5
- ${name})
6
- cmd+="__${name}"
7
- ;;`;
8
- function getBashCompletion(ctx) {
9
- const { cli } = ctx;
10
- const { _scriptName: name, _commands: commands } = cli;
11
- return `_${name}() {
12
- local i cur prev opts cmds
13
- COMPREPLY=()
14
- cur="\${COMP_WORDS[COMP_CWORD]}"
15
- prev="\${COMP_WORDS[COMP_CWORD-1]}"
16
- cmd=""
17
- opts=""
18
-
19
- for i in \${COMP_WORDS[@]}
20
- do
21
- case "\${i}" in
22
- "$1")
23
- cmd="${name}"
24
- ;;
25
- ${Object.keys(commands).map(generateCommandCompletion).join("")}
26
- *)
27
- ;;
28
- esac
29
- done
30
- }
31
-
32
- complete -F _${name} -o bashdefault -o default ${name}
33
- `;
5
+ //#region src/complete.ts
6
+ function splitCommand(cmd) {
7
+ const args = [];
8
+ let current = "";
9
+ let quote = null;
10
+ let escape = false;
11
+ for (const char of cmd) {
12
+ if (escape) {
13
+ current += char;
14
+ escape = false;
15
+ continue;
16
+ }
17
+ if (char === "\\") {
18
+ escape = true;
19
+ continue;
20
+ }
21
+ if (quote) if (char === quote) quote = null;
22
+ else current += char;
23
+ else if (char === "\"" || char === "'") quote = char;
24
+ else if (/\s/.test(char)) {
25
+ if (current) {
26
+ args.push(current);
27
+ current = "";
28
+ }
29
+ } else current += char;
30
+ }
31
+ if (current) args.push(current);
32
+ return args;
34
33
  }
35
-
36
- const NO_DESCRIPTION = "(No Description)";
37
- const getCompletionValue = (command) => `[CompletionResult]::new('${command.name}', '${command.name}', [CompletionResultType]::ParameterValue, '${command.description}')`;
38
- const getCompletionFlag = (command) => {
39
- var _a;
40
- return Object.entries((_a = command.flags) != null ? _a : {}).map(([flagName, flag]) => {
41
- const gen = [
42
- `[CompletionResult]::new('${gracefulFlagName(flagName)}', '${kebabCase(
43
- flagName
44
- )}', [CompletionResultType]::ParameterName, '${command.flags[flagName].description || NO_DESCRIPTION}')`
45
- ];
46
- if (flag == null ? void 0 : flag.alias) {
47
- gen.push(
48
- `[CompletionResult]::new('${gracefulFlagName(flag.alias)}', '${flag.alias}', [CompletionResultType]::ParameterName, '${command.flags[flagName].description || NO_DESCRIPTION}')`
49
- );
50
- }
51
- return gen.join("\n ");
52
- }).join("\n ");
53
- };
54
- function getPwshCompletion(ctx) {
55
- const { cli } = ctx;
56
- const { _scriptName: name, _commands: commands } = cli;
57
- return `using namespace System.Management.Automation
58
- using namespace System.Management.Automation.Language
59
-
60
- Register-ArgumentCompleter -Native -CommandName '${name}' -ScriptBlock {
61
- param($wordToComplete, $commandAst, $cursorPosition)
62
-
63
- $commandElements = $commandAst.CommandElements
64
- $command = @(
65
- '${name}'
66
- for ($i = 1; $i -lt $commandElements.Count; $i++) {
67
- $element = $commandElements[$i]
68
- if ($element -isnot [StringConstantExpressionAst] -or
69
- $element.StringConstantType -ne [StringConstantType]::BareWord -or
70
- $element.Value.StartsWith('-') -or
71
- $element.Value -eq $wordToComplete) {
72
- break
73
- }
74
- $element.Value
75
- }) -join ';'
76
-
77
- $completions = @(switch ($command) {
78
- '${name}' {
79
- ${Object.entries(commands).map(([_, command]) => getCompletionValue(command)).join("\n ")}
80
- break
81
- }
82
- ${Object.entries(commands).map(
83
- ([commandName, command]) => `'${name};${commandName.split(" ").join(";")}' {
84
- ${getCompletionFlag(command)}
85
- break
86
- }`
87
- ).join("\n ")}
88
- })
89
-
90
- $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
91
- Sort-Object -Property ListItemText
92
- }`;
34
+ async function getCompletion(cli, env) {
35
+ const inputArgv = splitCommand(env.partial.slice(0, env.partial.length - env.lastPartial.length)).slice(1);
36
+ if (inputArgv.includes("--")) return [];
37
+ const [command, commandName] = resolveCommand(cli._commands, inputArgv);
38
+ if (env.lastPartial.startsWith("-")) {
39
+ const flags = command ? {
40
+ ...cli._globalFlags,
41
+ ...command.flags
42
+ } : cli._globalFlags;
43
+ const candidates$1 = [];
44
+ for (const [name, def] of Object.entries(flags)) {
45
+ candidates$1.push({
46
+ name: formatFlagName(name),
47
+ description: def.description
48
+ });
49
+ if (def.alias) {
50
+ const aliases = toArray(def.alias);
51
+ for (const alias of aliases) candidates$1.push({
52
+ name: formatFlagName(alias),
53
+ description: def.description
54
+ });
55
+ }
56
+ }
57
+ return candidates$1;
58
+ }
59
+ const candidates = [];
60
+ let prefix = "";
61
+ if (commandName) {
62
+ const matchedParts = commandName.split(" ");
63
+ const remainingArgs = inputArgv.slice(matchedParts.length);
64
+ prefix = `${[command.name, ...remainingArgs].join(" ")} `;
65
+ } else prefix = inputArgv.length > 0 ? `${inputArgv.join(" ")} ` : "";
66
+ for (const c of cli._commands.values()) if (c.name.startsWith(prefix)) {
67
+ const nextWord = c.name.slice(prefix.length).split(" ")[0];
68
+ if (nextWord) candidates.push({
69
+ name: nextWord,
70
+ description: c.description
71
+ });
72
+ }
73
+ const uniqueCandidates = /* @__PURE__ */ new Map();
74
+ for (const c of candidates) uniqueCandidates.set(c.name, c);
75
+ return [...uniqueCandidates.values()];
93
76
  }
94
77
 
95
- const completionMap = {
96
- bash: getBashCompletion,
97
- pwsh: getPwshCompletion
98
- };
99
- const completionsPlugin = (options = {}) => definePlugin({
100
- setup: (cli) => {
101
- const { command = true } = options;
102
- if (command) {
103
- cli = cli.command("completions", "Print shell completions to stdout", {
104
- flags: {
105
- shell: {
106
- description: "Shell type",
107
- type: String,
108
- default: ""
109
- }
110
- },
111
- parameters: ["[shell]"]
112
- }).on("completions", (ctx) => {
113
- var _a;
114
- if (!cli._scriptName) {
115
- throw new Error("CLI name is not defined!");
116
- }
117
- const shell = String((_a = ctx.parameters.shell) != null ? _a : ctx.flags.shell);
118
- if (!shell) {
119
- throw new Error("Missing shell name");
120
- }
121
- if (shell in completionMap) {
122
- process.stdout.write(
123
- completionMap[shell](ctx)
124
- );
125
- } else {
126
- throw new Error(`No such shell: ${shell}`);
127
- }
128
- });
129
- }
130
- return cli;
131
- }
132
- });
78
+ //#endregion
79
+ //#region src/index.ts
80
+ const completionsPlugin = (options = {}) => definePlugin({ setup: (cli) => {
81
+ const { managementCommands = true } = options;
82
+ if (managementCommands) {
83
+ cli.command("completions install", "Install shell completions", {
84
+ flags: { shell: {
85
+ description: "Shell type",
86
+ type: String
87
+ } },
88
+ parameters: ["[shell]"]
89
+ }).on("completions install", async (ctx) => {
90
+ const shell = ctx.parameters.shell ?? ctx.flags.shell;
91
+ if (!shell) throw new Error("Please specify the shell type via the --shell flag or the [shell] parameter.");
92
+ if (!tabtab.SUPPORTED_SHELLS.includes(shell)) throw new Error(`Unsupported shell: ${shell}. Supported shells are: ${tabtab.SUPPORTED_SHELLS.join(", ")}.`);
93
+ await tabtab.install({
94
+ name: cli._name,
95
+ completer: cli._name,
96
+ shell
97
+ });
98
+ });
99
+ cli.command("completions uninstall", "Uninstall shell completions").on("completions uninstall", async () => {
100
+ await tabtab.uninstall({ name: cli._name });
101
+ });
102
+ }
103
+ cli.command("completions", "Generate completions script", {
104
+ flags: { shell: {
105
+ description: "Shell type",
106
+ type: String
107
+ } },
108
+ parameters: ["[shell]"]
109
+ }).on("completions", async (ctx) => {
110
+ const shell = ctx.parameters.shell ?? ctx.flags.shell;
111
+ if (!shell) throw new Error("Please specify the shell type via the --shell flag or the [shell] parameter.");
112
+ if (!tabtab.SUPPORTED_SHELLS.includes(shell)) throw new Error(`Unsupported shell: ${shell}. Supported shells are: ${tabtab.SUPPORTED_SHELLS.join(", ")}.`);
113
+ const script = await tabtab.getCompletionScript({
114
+ name: cli._name,
115
+ completer: cli._name,
116
+ shell
117
+ });
118
+ console.log(script);
119
+ });
120
+ cli.command("completion-server", "Handle completions", { help: { showInHelp: false } }).on("completion-server", async () => {
121
+ const env = tabtab.parseEnv(process.env);
122
+ if (!env.complete) return;
123
+ const shell = getShellFromEnv(process.env);
124
+ const filtered = (await getCompletion(cli, env)).filter((c) => c.name.startsWith(env.lastPartial));
125
+ tabtab.log(filtered, shell);
126
+ });
127
+ return cli;
128
+ } });
133
129
 
134
- export { completionsPlugin };
130
+ //#endregion
131
+ export { completionsPlugin };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clerc/plugin-completions",
3
- "version": "0.44.0",
3
+ "version": "1.0.0-beta.2",
4
4
  "author": "Ray <i@mk1.io> (https://github.com/so1ve)",
5
5
  "type": "module",
6
6
  "description": "Clerc plugin completions",
@@ -25,13 +25,10 @@
25
25
  "license": "MIT",
26
26
  "sideEffects": false,
27
27
  "exports": {
28
- ".": {
29
- "types": "./dist/index.d.ts",
30
- "import": "./dist/index.js"
31
- }
28
+ ".": "./dist/index.js"
32
29
  },
33
- "main": "dist/index.js",
34
- "module": "dist/index.js",
30
+ "main": "./dist/index.js",
31
+ "module": "./dist/index.js",
35
32
  "types": "dist/index.d.ts",
36
33
  "typesVersions": {
37
34
  "*": {
@@ -48,13 +45,17 @@
48
45
  "access": "public"
49
46
  },
50
47
  "dependencies": {
51
- "@clerc/utils": "0.44.0"
48
+ "@pnpm/tabtab": "^0.5.4",
49
+ "@clerc/utils": "1.0.0-beta.2"
50
+ },
51
+ "devDependencies": {
52
+ "@clerc/core": "1.0.0-beta.2"
52
53
  },
53
54
  "peerDependencies": {
54
55
  "@clerc/core": "*"
55
56
  },
56
57
  "scripts": {
57
- "build": "pkgroll",
58
- "watch": "pkgroll --watch"
58
+ "build": "tsdown",
59
+ "watch": "tsdown --watch"
59
60
  }
60
61
  }