Skip to content

Commit d2f8ca7

Browse files
catullhansl
authored andcommitted
feat(@angular/cli): Generate completion.sh automatically.
It requires little tweaking in the case-block. Now the completion shell script is generated out of TypeScript code entirely. The options and aliases are generated dynamically. There are options to only produce bash- or zsh-specific code. Closes #3981.
1 parent 3b62a93 commit d2f8ca7

File tree

3 files changed

+172
-93
lines changed

3 files changed

+172
-93
lines changed

README.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -285,19 +285,19 @@ To turn on auto completion use the following commands:
285285

286286
For bash:
287287
```bash
288-
ng completion 1>> ~/.bashrc 2>>&1
288+
ng completion --bash >> ~/.bashrc
289289
source ~/.bashrc
290290
```
291291

292292
For zsh:
293293
```bash
294-
ng completion 1>> ~/.zshrc 2>>&1
294+
ng completion --zsh >> ~/.zshrc
295295
source ~/.zshrc
296296
```
297297

298298
Windows users using gitbash:
299299
```bash
300-
ng completion 1>> ~/.bash_profile 2>>&1
300+
ng completion --bash >> ~/.bash_profile
301301
source ~/.bash_profile
302302
```
303303

packages/@angular/cli/commands/completion.ts

+169-5
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,181 @@
1-
import * as path from 'path';
21
import * as fs from 'fs';
2+
import * as path from 'path';
3+
4+
import { oneLine, stripIndent } from 'common-tags';
35

6+
const stringUtils = require('ember-cli-string-utils');
47
const Command = require('../ember-cli/lib/models/command');
8+
const lookupCommand = require('../ember-cli/lib/cli/lookup-command');
9+
10+
function extractOptions(opts: any): String {
11+
const output: String[] = [];
12+
13+
for (let index = 0; index < opts.length; index++) {
14+
const element = opts[index];
15+
output.push('--' + element.name);
16+
if (element.aliases) {
17+
output.push('-' + element.aliases[0]);
18+
}
19+
}
20+
21+
return output.sort().join(' ');
22+
}
23+
24+
export interface CompletionCommandOptions {
25+
all?: boolean;
26+
bash?: boolean;
27+
zsh?: boolean;
28+
};
29+
30+
const commandsToIgnore = [
31+
'easter-egg',
32+
'destroy',
33+
'github-pages-deploy' // errors because there is no base github-pages command
34+
];
35+
36+
const optsNg: String[] = [];
537

638
const CompletionCommand = Command.extend({
739
name: 'completion',
840
description: 'Adds autocomplete functionality to `ng` commands and subcommands',
941
works: 'everywhere',
10-
run: function() {
11-
const scriptPath = path.resolve(__dirname, '..', 'utilities', 'completion.sh');
12-
const scriptOutput = fs.readFileSync(scriptPath, 'utf8');
42+
availableOptions: [
43+
{ name: 'all', type: Boolean, default: true, aliases: ['a'] },
44+
{ name: 'bash', type: Boolean, default: false, aliases: ['b'] },
45+
{ name: 'zsh', type: Boolean, default: false, aliases: ['z'] }
46+
],
47+
48+
run: function (commandOptions: CompletionCommandOptions) {
49+
commandOptions.all = !commandOptions.bash && !commandOptions.zsh;
50+
51+
const commandFiles = fs.readdirSync(__dirname)
52+
.filter(file => file.match(/\.ts$/) && !file.match(/\.run.ts$/))
53+
.map(file => path.parse(file).name)
54+
.filter(file => {
55+
return commandsToIgnore.indexOf(file) < 0;
56+
})
57+
.map(file => file.toLowerCase());
58+
59+
const commandMap = commandFiles.reduce((acc: any, curr: string) => {
60+
let classifiedName = stringUtils.classify(curr);
61+
let defaultImport = require(`./${curr}`).default;
62+
63+
acc[classifiedName] = defaultImport;
64+
65+
return acc;
66+
}, {});
67+
68+
let caseBlock = '';
69+
70+
commandFiles.forEach(cmd => {
71+
const Command = lookupCommand(commandMap, cmd);
72+
const com: String[] = [];
73+
74+
const command = new Command({
75+
ui: this.ui,
76+
project: this.project,
77+
commands: this.commands,
78+
tasks: this.tasks
79+
});
80+
81+
optsNg.push(command.name);
82+
com.push(command.name);
83+
84+
if (command.aliases) {
85+
command.aliases.forEach((element: String) => {
86+
optsNg.push(element);
87+
com.push(element);
88+
});
89+
}
90+
91+
if (command.availableOptions && command.availableOptions[0]) {
92+
let opts = extractOptions (command.availableOptions);
93+
caseBlock = caseBlock + ' ' + com.sort().join('|') + ') opts="' + opts + '" ;;\n';
94+
}
95+
});
96+
97+
caseBlock = 'ng|help) opts="' + optsNg.sort().join(' ') + '" ;;\n' +
98+
caseBlock +
99+
' *) opts="" ;;';
100+
101+
console.log(stripIndent`
102+
###-begin-ng-completion###
103+
#
104+
105+
# ng command completion script
106+
# This command supports 3 cases.
107+
# 1. (Default case) It prints a common completion initialisation for both Bash and Zsh.
108+
# It is the result of either calling "ng completion" or "ng completion -a".
109+
# 2. Produce Bash-only completion: "ng completion -b" or "ng completion --bash".
110+
# 3. Produce Zsh-only completion: "ng completion -z" or "ng completion --zsh".
111+
#
112+
# Installation: ng completion -b >> ~/.bashrc
113+
# or ng completion -z >> ~/.zshrc
114+
#`);
115+
116+
if (commandOptions.all && !commandOptions.bash) {
117+
console.log('if test ".$(type -t complete 2>/dev/null || true)" = ".builtin"; then');
118+
}
119+
120+
if (commandOptions.all || commandOptions.bash) {
121+
console.log(stripIndent`
122+
_ng_completion() {
123+
local cword pword opts
124+
125+
COMPREPLY=()
126+
cword=\${COMP_WORDS[COMP_CWORD]}
127+
pword=\${COMP_WORDS[COMP_CWORD - 1]}
128+
129+
case \${pword} in
130+
${caseBlock}
131+
esac
132+
133+
COMPREPLY=( $(compgen -W '\${opts}' -- $cword) )
134+
135+
return 0
136+
}
137+
138+
complete -o default -F _ng_completion ng
139+
`);
140+
}
141+
142+
if (commandOptions.all) {
143+
console.log(stripIndent`
144+
elif test ".$(type -w compctl 2>/dev/null || true)" = ".compctl: builtin" ; then
145+
`);
146+
}
147+
148+
if (commandOptions.all || commandOptions.zsh) {
149+
console.log(stripIndent`
150+
_ng_completion () {
151+
local words cword opts
152+
read -Ac words
153+
read -cn cword
154+
let cword-=1
155+
156+
case $words[cword] in
157+
${caseBlock}
158+
esac
159+
160+
setopt shwordsplit
161+
reply=($opts)
162+
unset shwordsplit
163+
}
164+
165+
compctl -K _ng_completion ng
166+
`);
167+
}
168+
169+
if (commandOptions.all) {
170+
console.log(stripIndent`
171+
else
172+
echo "Builtin command 'complete' or 'compctl' is redefined; cannot produce completion."
173+
return 1
174+
fi`);
175+
}
176+
177+
console.log('###-end-ng-completion###');
13178

14-
console.log(scriptOutput);
15179
}
16180
});
17181

packages/@angular/cli/utilities/completion.sh

-85
This file was deleted.

0 commit comments

Comments
 (0)