Skip to content

Commit b54b323

Browse files
committed
feat: add fish.ts & bash.ts
1 parent d5659d5 commit b54b323

File tree

10 files changed

+845
-214
lines changed

10 files changed

+845
-214
lines changed

README.md

Lines changed: 65 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,16 @@ Shell autocompletions are largely missing in the javascript cli ecosystem. This
66

77
Tools like git and their autocompletion experience inspired us to build this tool and make the same ability available for any javascript cli project. Developers love hitting the tab key, hence why they prefer tabs over spaces.
88

9+
## Examples
10+
11+
Check out the [examples directory](./examples) for complete examples of using Tab with different command-line frameworks:
12+
13+
- [CAC](./examples/demo.cac.ts)
14+
- [Citty](./examples/demo.citty.ts)
15+
- [Commander.js](./examples/demo.commander.ts)
16+
17+
## Usage
18+
919
```ts
1020
import { Completion, script } from '@bombsh/tab';
1121

@@ -146,6 +156,47 @@ const cli = createMain(main);
146156
cli();
147157
```
148158

159+
### `@bombsh/tab/commander`
160+
161+
```ts
162+
import { Command } from 'commander';
163+
import tab from '@bombsh/tab/commander';
164+
165+
const program = new Command('my-cli');
166+
program.version('1.0.0');
167+
168+
// Add commands
169+
program
170+
.command('serve')
171+
.description('Start the server')
172+
.option('-p, --port <number>', 'port to use', '3000')
173+
.option('-H, --host <host>', 'host to use', 'localhost')
174+
.action((options) => {
175+
console.log('Starting server...');
176+
});
177+
178+
// Initialize tab completion
179+
const completion = tab(program);
180+
181+
// Configure custom completions
182+
for (const command of completion.commands.values()) {
183+
if (command.name === 'serve') {
184+
for (const [option, config] of command.options.entries()) {
185+
if (option === '--port') {
186+
config.handler = () => {
187+
return [
188+
{ value: '3000', description: 'Default port' },
189+
{ value: '8080', description: 'Alternative port' },
190+
];
191+
};
192+
}
193+
}
194+
}
195+
}
196+
197+
program.parse();
198+
```
199+
149200
## Recipe
150201

151202
`source <(my-cli complete zsh)` won't be enough since the user would have to run this command each time they spin up a new shell instance.
@@ -157,6 +208,20 @@ my-cli completion zsh > ~/completion-for-my-cli.zsh
157208
echo 'source ~/completion-for-my-cli.zsh' >> ~/.zshrc
158209
```
159210

211+
For other shells:
212+
213+
```bash
214+
# Bash
215+
my-cli complete bash > ~/.bash_completion.d/my-cli
216+
echo 'source ~/.bash_completion.d/my-cli' >> ~/.bashrc
217+
218+
# Fish
219+
my-cli complete fish > ~/.config/fish/completions/my-cli.fish
220+
221+
# PowerShell
222+
my-cli complete powershell > $PROFILE.CurrentUserAllHosts
223+
```
224+
160225
## Autocompletion Server
161226

162227
By integrating tab into your cli, your cli would have a new command called `complete`. This is where all the magic happens. And the shell would contact this command to get completions. That's why we call it the autocompletion server.
@@ -181,8 +246,3 @@ Other package managers like `npm` and `yarn` can decide whether to support this
181246

182247
- git
183248
- [cobra](https://github.com/spf13/cobra/blob/main/shell_completions.go), without cobra, tab would have took 10x longer to build
184-
185-
## TODO
186-
187-
- [] fish
188-
- [] bash

demo.citty.ts

Lines changed: 0 additions & 128 deletions
This file was deleted.

demo.cac.ts renamed to examples/demo.cac.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import cac from 'cac';
2-
import tab from './src/cac';
2+
import tab from '../src/cac';
33

44
const cli = cac('vite');
55

examples/demo.citty.ts

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { defineCommand, createMain } from 'citty';
2+
import tab from '../src/citty';
3+
4+
const main = defineCommand({
5+
meta: {
6+
name: 'vite',
7+
version: '0.0.0',
8+
description: 'Vite CLI',
9+
},
10+
args: {
11+
config: {
12+
type: 'string',
13+
description: 'Use specified config file',
14+
alias: 'c',
15+
},
16+
mode: {
17+
type: 'string',
18+
description: 'Set env mode',
19+
alias: 'm',
20+
},
21+
logLevel: {
22+
type: 'string',
23+
description: 'info | warn | error | silent',
24+
alias: 'l',
25+
},
26+
},
27+
subCommands: {
28+
dev: defineCommand({
29+
meta: {
30+
name: 'dev',
31+
description: 'Start dev server',
32+
},
33+
args: {
34+
host: {
35+
type: 'string',
36+
description: 'Specify hostname',
37+
alias: 'H',
38+
},
39+
port: {
40+
type: 'string',
41+
description: 'Specify port',
42+
alias: 'p',
43+
},
44+
},
45+
run: () => {},
46+
}),
47+
build: defineCommand({
48+
meta: {
49+
name: 'build',
50+
description: 'Build project',
51+
},
52+
run: () => {},
53+
}),
54+
lint: defineCommand({
55+
meta: {
56+
name: 'lint',
57+
description: 'Lint project',
58+
},
59+
args: {
60+
files: {
61+
type: 'positional',
62+
description: 'Files to lint',
63+
required: false,
64+
},
65+
},
66+
run: () => {},
67+
}),
68+
},
69+
run: () => {},
70+
});
71+
72+
const completion = await tab(main);
73+
74+
for (const command of completion.commands.values()) {
75+
if (command.name === 'lint') {
76+
command.handler = () => {
77+
return [
78+
{ value: 'main.ts', description: 'Main file' },
79+
{ value: 'index.ts', description: 'Index file' },
80+
];
81+
};
82+
}
83+
84+
for (const [o, config] of command.options.entries()) {
85+
if (o === '--port') {
86+
config.handler = () => {
87+
return [
88+
{ value: '3000', description: 'Development server port' },
89+
{ value: '8080', description: 'Alternative port' },
90+
];
91+
};
92+
}
93+
if (o === '--host') {
94+
config.handler = () => {
95+
return [
96+
{ value: 'localhost', description: 'Localhost' },
97+
{ value: '0.0.0.0', description: 'All interfaces' },
98+
];
99+
};
100+
}
101+
if (o === '--config') {
102+
config.handler = () => {
103+
return [
104+
{ value: 'vite.config.ts', description: 'Vite config file' },
105+
{ value: 'vite.config.js', description: 'Vite config file' },
106+
];
107+
};
108+
}
109+
if (o === '--mode') {
110+
config.handler = () => {
111+
return [
112+
{ value: 'development', description: 'Development mode' },
113+
{ value: 'production', description: 'Production mode' },
114+
];
115+
};
116+
}
117+
if (o === '--logLevel') {
118+
config.handler = () => {
119+
return [
120+
{ value: 'info', description: 'Info level' },
121+
{ value: 'warn', description: 'Warn level' },
122+
{ value: 'error', description: 'Error level' },
123+
{ value: 'silent', description: 'Silent level' },
124+
];
125+
};
126+
}
127+
}
128+
}
129+
130+
// Create the CLI and run it
131+
const cli = createMain(main);
132+
cli();
Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
import { Command } from 'commander';
2-
import tab from './src/commander';
2+
import tab from '../src/commander';
3+
4+
// Define the Item type locally
5+
interface Item {
6+
value: string;
7+
description: string;
8+
}
39

410
// Create a new Commander program
511
const program = new Command('myapp');
@@ -111,17 +117,9 @@ for (const command of completion.commands.values()) {
111117
if (process.argv[2] === 'test-completion') {
112118
const args = process.argv.slice(3);
113119
console.log('Testing completion with args:', args);
114-
115-
// Special case for deploy command with a space at the end
116-
if (args.length === 1 && args[0] === 'deploy ') {
117-
console.log('staging Deploy to staging environment');
118-
console.log('production Deploy to production environment');
119-
console.log(':2');
120-
} else {
121-
completion.parse(args).then(() => {
122-
// Done
123-
});
124-
}
120+
completion.parse(args).then(() => {
121+
// Done
122+
});
125123
} else {
126124
// Parse command line arguments
127125
program.parse();

0 commit comments

Comments
 (0)