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

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 +25 -5
  2. package/dist/index.js +135 -128
  3. package/package.json +9 -12
package/dist/index.d.ts CHANGED
@@ -1,8 +1,28 @@
1
- import * as _clerc_core from '@clerc/core';
1
+ import { Plugin } from "@clerc/core";
2
2
 
3
+ //#region src/index.d.ts
4
+ declare module "@clerc/core" {
5
+ interface CommandCustomOptions {
6
+ /**
7
+ * Completions options for the command.
8
+ */
9
+ completions?: {
10
+ /**
11
+ * Whether to show the command in completions output.
12
+ *
13
+ * @default true
14
+ */
15
+ show?: boolean;
16
+ };
17
+ }
18
+ }
3
19
  interface CompletionsPluginOptions {
4
- command?: boolean;
20
+ /**
21
+ * Whether to register the `completions install` and `completions uninstall` commands.
22
+ * @default true
23
+ */
24
+ managementCommands?: boolean;
5
25
  }
6
- declare const completionsPlugin: (options?: CompletionsPluginOptions) => _clerc_core.Plugin<_clerc_core.Clerc<{}, {}>, _clerc_core.Clerc<{}, {}>>;
7
-
8
- export { CompletionsPluginOptions, completionsPlugin };
26
+ declare const completionsPlugin: (options?: CompletionsPluginOptions) => Plugin;
27
+ //#endregion
28
+ export { CompletionsPluginOptions, completionsPlugin };
package/dist/index.js CHANGED
@@ -1,134 +1,141 @@
1
- import { definePlugin } from '@clerc/core';
2
- import { gracefulFlagName, kebabCase } from '@clerc/utils';
1
+ import { DOUBLE_DASH, definePlugin, resolveCommand } from "@clerc/core";
2
+ import tabtab, { getShellFromEnv } from "@pnpm/tabtab";
3
3
 
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=""
4
+ //#region ../utils/src/index.ts
5
+ const toArray = (a) => Array.isArray(a) ? a : [a];
6
+ const kebabCase = (s) => s.replace(/([A-Z])/g, (_, c) => `-${c.toLowerCase()}`);
7
+ const formatFlagName = (n) => n.length <= 1 ? `-${n}` : `--${kebabCase(n)}`;
18
8
 
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
9
+ //#endregion
10
+ //#region src/complete.ts
11
+ function splitCommand(cmd) {
12
+ const args = [];
13
+ let current = "";
14
+ let quote = null;
15
+ let escape = false;
16
+ for (const char of cmd) {
17
+ if (escape) {
18
+ current += char;
19
+ escape = false;
20
+ continue;
21
+ }
22
+ if (char === "\\") {
23
+ escape = true;
24
+ continue;
25
+ }
26
+ if (quote) if (char === quote) quote = null;
27
+ else current += char;
28
+ else if (char === "\"" || char === "'") quote = char;
29
+ else if (/\s/.test(char)) {
30
+ if (current) {
31
+ args.push(current);
32
+ current = "";
33
+ }
34
+ } else current += char;
35
+ }
36
+ if (current) args.push(current);
37
+ return args;
30
38
  }
31
-
32
- complete -F _${name} -o bashdefault -o default ${name}
33
- `;
34
- }
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
- }`;
39
+ async function getCompletion(cli, env) {
40
+ const inputArgv = splitCommand(env.partial.slice(0, env.partial.length - env.lastPartial.length)).slice(1);
41
+ if (inputArgv.includes(DOUBLE_DASH)) return [];
42
+ const [command, commandName] = resolveCommand(cli._commands, inputArgv);
43
+ if (env.lastPartial.startsWith("-")) {
44
+ const flags = command ? {
45
+ ...cli._globalFlags,
46
+ ...command.flags
47
+ } : cli._globalFlags;
48
+ const candidates$1 = [];
49
+ for (const [name, def] of Object.entries(flags)) {
50
+ candidates$1.push({
51
+ name: formatFlagName(name),
52
+ description: def.description
53
+ });
54
+ if (def.alias) {
55
+ const aliases = toArray(def.alias);
56
+ for (const alias of aliases) candidates$1.push({
57
+ name: formatFlagName(alias),
58
+ description: def.description
59
+ });
60
+ }
61
+ }
62
+ return candidates$1;
63
+ }
64
+ const candidates = [];
65
+ let prefix = "";
66
+ if (commandName) {
67
+ const matchedParts = commandName.split(" ");
68
+ const remainingArgs = inputArgv.slice(matchedParts.length);
69
+ prefix = `${[command.name, ...remainingArgs].join(" ")} `;
70
+ } else prefix = inputArgv.length > 0 ? `${inputArgv.join(" ")} ` : "";
71
+ for (const command$1 of cli._commands.values()) {
72
+ if (command$1.completions?.show === false) continue;
73
+ if (command$1.name.startsWith(prefix)) {
74
+ const nextWord = command$1.name.slice(prefix.length).split(" ")[0];
75
+ if (nextWord) candidates.push({
76
+ name: nextWord,
77
+ description: command$1.description
78
+ });
79
+ }
80
+ }
81
+ const uniqueCandidates = /* @__PURE__ */ new Map();
82
+ for (const c of candidates) uniqueCandidates.set(c.name, c);
83
+ return [...uniqueCandidates.values()];
93
84
  }
94
85
 
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
- });
86
+ //#endregion
87
+ //#region src/index.ts
88
+ const completionsPlugin = (options = {}) => definePlugin({ setup: (cli) => {
89
+ const { managementCommands = true } = options;
90
+ if (managementCommands) {
91
+ cli.command("completions install", "Install shell completions", {
92
+ help: { group: "completions" },
93
+ flags: { shell: {
94
+ description: "Shell type",
95
+ type: String
96
+ } },
97
+ parameters: ["[shell]"]
98
+ }).on("completions install", async (ctx) => {
99
+ const shell = ctx.parameters.shell ?? ctx.flags.shell;
100
+ if (!shell) throw new Error("Please specify the shell type via the --shell flag or the [shell] parameter.");
101
+ if (!tabtab.SUPPORTED_SHELLS.includes(shell)) throw new Error(`Unsupported shell: ${shell}. Supported shells are: ${tabtab.SUPPORTED_SHELLS.join(", ")}.`);
102
+ await tabtab.install({
103
+ name: cli._name,
104
+ completer: cli._name,
105
+ shell
106
+ });
107
+ });
108
+ cli.command("completions uninstall", "Uninstall shell completions", { help: { group: "completions" } }).on("completions uninstall", async () => {
109
+ await tabtab.uninstall({ name: cli._name });
110
+ });
111
+ }
112
+ cli.command("completions", "Generate completions script", {
113
+ help: { group: "completions" },
114
+ flags: { shell: {
115
+ description: "Shell type",
116
+ type: String
117
+ } },
118
+ parameters: ["[shell]"]
119
+ }).on("completions", async (ctx) => {
120
+ const shell = ctx.parameters.shell ?? ctx.flags.shell;
121
+ if (!shell) throw new Error("Please specify the shell type via the --shell flag or the [shell] parameter.");
122
+ if (!tabtab.SUPPORTED_SHELLS.includes(shell)) throw new Error(`Unsupported shell: ${shell}. Supported shells are: ${tabtab.SUPPORTED_SHELLS.join(", ")}.`);
123
+ const script = await tabtab.getCompletionScript({
124
+ name: cli._name,
125
+ completer: cli._name,
126
+ shell
127
+ });
128
+ console.log(script);
129
+ });
130
+ cli.command("completion-server", "Handle completions", { help: { show: false } }).on("completion-server", async () => {
131
+ const env = tabtab.parseEnv(process.env);
132
+ if (!env.complete) return;
133
+ const shell = getShellFromEnv(process.env);
134
+ const filtered = (await getCompletion(cli, env)).filter((c) => c.name.startsWith(env.lastPartial));
135
+ tabtab.log(filtered, shell);
136
+ });
137
+ return cli;
138
+ } });
133
139
 
134
- export { completionsPlugin };
140
+ //#endregion
141
+ 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.10",
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,13 @@
48
45
  "access": "public"
49
46
  },
50
47
  "dependencies": {
51
- "@clerc/utils": "0.44.0"
48
+ "@pnpm/tabtab": "^0.5.4"
49
+ },
50
+ "devDependencies": {
51
+ "@clerc/core": "1.0.0-beta.10",
52
+ "@clerc/utils": "1.0.0-beta.10"
52
53
  },
53
54
  "peerDependencies": {
54
55
  "@clerc/core": "*"
55
- },
56
- "scripts": {
57
- "build": "pkgroll",
58
- "watch": "pkgroll --watch"
59
56
  }
60
57
  }