Skip to content

Commit af74cec

Browse files
authored
feat: add installation process for bash (#8)
1 parent d7cdfd5 commit af74cec

File tree

5 files changed

+194
-10
lines changed

5 files changed

+194
-10
lines changed

lib/src/install/shell_completion_configuration.dart

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class ShellCompletionConfiguration {
2828
final String name;
2929

3030
/// The location of a config file that is run upon shell start.
31-
/// Eg: .bashrc or .zshrc
31+
/// Eg: .bash_profile or .zshrc
3232
final String shellRCFile;
3333

3434
/// Generates a line to sources of a script file.
@@ -68,3 +68,41 @@ fi
6868
''';
6969
},
7070
);
71+
72+
/// A [ShellCompletionConfiguration] for bash.
73+
final bashConfiguration = ShellCompletionConfiguration(
74+
name: 'bash',
75+
shellRCFile: '~/.bash_profile',
76+
sourceLineTemplate: (String scriptPath) {
77+
return '[ -f $scriptPath ] && . $scriptPath || true';
78+
},
79+
scriptTemplate: (String rootCommand) {
80+
// Completion script for bash.
81+
//
82+
// Based on https://github.com/mklabs/tabtab/blob/master/lib/scripts/bash.sh
83+
return '''
84+
if type complete &>/dev/null; then
85+
_${rootCommand}_completion () {
86+
local words cword
87+
if type _get_comp_words_by_ref &>/dev/null; then
88+
_get_comp_words_by_ref -n = -n @ -n : -w words -i cword
89+
else
90+
cword="\$COMP_CWORD"
91+
words=("\${COMP_WORDS[@]}")
92+
fi
93+
local si="\$IFS"
94+
IFS=\$'\n' COMPREPLY=(\$(COMP_CWORD="\$cword" \\
95+
COMP_LINE="\$COMP_LINE" \\
96+
COMP_POINT="\$COMP_POINT" \\
97+
$rootCommand completion -- "\${words[@]}" \\
98+
2>/dev/null)) || return \$?
99+
IFS="\$si"
100+
if type __ltrim_colon_completions &>/dev/null; then
101+
__ltrim_colon_completions "\${words[cword]}"
102+
fi
103+
}
104+
complete -o default -F _${rootCommand}_completion $rootCommand
105+
fi
106+
''';
107+
},
108+
);

lib/src/install/shell_completion_installation.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,9 @@ class ShellCompletionInstallation {
4242
final ShellCompletionConfiguration configuration;
4343
if (basename == 'zsh') {
4444
configuration = zshConfiguration;
45+
} else if (RegExp(r'bash(\.exe)?$').hasMatch(basename)) {
46+
// on windows basename can be bash.exe
47+
configuration = bashConfiguration;
4548
} else {
4649
return null;
4750
}

test/src/install/install_completion_test.dart

Lines changed: 35 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,41 @@ void main() {
4444
),
4545
);
4646

47-
expect(tempDir.listSync().map((e) => path.basename(e.path)), [
48-
'.zshrc',
49-
'.dart-cli-completion',
50-
]);
47+
expect(
48+
tempDir.listSync().map((e) => path.basename(e.path)),
49+
unorderedEquals(['.zshrc', '.dart-cli-completion']),
50+
);
51+
});
52+
53+
test('installs for bash', () {
54+
File(path.join(tempDir.path, '.bash_profile')).createSync();
55+
56+
installCompletion(
57+
logger: logger,
58+
rootCommand: 'very_good',
59+
isWindowsOverride: false,
60+
environmentOverride: {
61+
'SHELL': '/foo/bar/bash',
62+
'HOME': tempDir.path,
63+
},
64+
);
65+
66+
verify(
67+
() => logger.detail(
68+
'Completion installation for very_good started',
69+
),
70+
);
71+
72+
verify(
73+
() => logger.detail(
74+
'Shell identified as bash',
75+
),
76+
);
77+
78+
expect(
79+
tempDir.listSync().map((e) => path.basename(e.path)),
80+
unorderedEquals(['.dart-cli-completion', '.bash_profile']),
81+
);
5182
});
5283

5384
test('do nothing on unknown shells', () {

test/src/install/shell_completion_configuration_test.dart

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,5 +42,55 @@ fi
4242
);
4343
});
4444
});
45+
46+
group('bashConfiguration', () {
47+
test('name', () {
48+
expect(bashConfiguration.name, 'bash');
49+
});
50+
51+
test('shellRCFile', () {
52+
expect(bashConfiguration.shellRCFile, '~/.bash_profile');
53+
});
54+
55+
test('sourceStringTemplate', () {
56+
final result = bashConfiguration.sourceLineTemplate('./pans/snaps');
57+
expect(result, '[ -f ./pans/snaps ] && . ./pans/snaps || true');
58+
});
59+
60+
test('completionScriptTemplate', () {
61+
final result = bashConfiguration.scriptTemplate('very_good');
62+
expect(result, '''
63+
if type complete &>/dev/null; then
64+
_very_good_completion () {
65+
local words cword
66+
if type _get_comp_words_by_ref &>/dev/null; then
67+
_get_comp_words_by_ref -n = -n @ -n : -w words -i cword
68+
else
69+
cword="\$COMP_CWORD"
70+
words=("\${COMP_WORDS[@]}")
71+
fi
72+
local si="\$IFS"
73+
IFS=\$'\n' COMPREPLY=(\$(COMP_CWORD="\$cword" \\
74+
COMP_LINE="\$COMP_LINE" \\
75+
COMP_POINT="\$COMP_POINT" \\
76+
very_good completion -- "\${words[@]}" \\
77+
2>/dev/null)) || return \$?
78+
IFS="\$si"
79+
if type __ltrim_colon_completions &>/dev/null; then
80+
__ltrim_colon_completions "\${words[cword]}"
81+
fi
82+
}
83+
complete -o default -F _very_good_completion very_good
84+
fi
85+
''');
86+
});
87+
88+
test('completionConfigForShellFileName', () {
89+
expect(
90+
bashConfiguration.completionConfigForShellFileName,
91+
'bash-config.bash',
92+
);
93+
});
94+
});
4595
});
4696
}

test/src/install/shell_completion_installation_test.dart

Lines changed: 67 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,29 @@ void main() {
4040
expect(result?.configuration, zshConfiguration);
4141
});
4242

43+
test('identifies bash shell', () {
44+
final logger = MockLogger();
45+
final result = ShellCompletionInstallation.fromCurrentShell(
46+
logger: logger,
47+
isWindowsOverride: false,
48+
environmentOverride: {
49+
'SHELL': '/foo/bar/bash',
50+
},
51+
);
52+
53+
expect(result?.configuration, bashConfiguration);
54+
55+
final resultWindows = ShellCompletionInstallation.fromCurrentShell(
56+
logger: logger,
57+
isWindowsOverride: true,
58+
environmentOverride: {
59+
'SHELL': r'c:\foo\bar\bash.exe',
60+
},
61+
);
62+
63+
expect(resultWindows?.configuration, bashConfiguration);
64+
});
65+
4366
group('identifies no shell', () {
4467
test('for no shell env', () {
4568
final result = ShellCompletionInstallation.fromCurrentShell(
@@ -401,11 +424,50 @@ void main() {
401424
402425
''');
403426

404-
expect(configDir.listSync().map((e) => path.basename(e.path)), [
405-
'not_good.zsh',
406-
'very_good.zsh',
407-
'zsh-config.zsh',
408-
]);
427+
expect(
428+
configDir.listSync().map((e) => path.basename(e.path)),
429+
unorderedEquals([
430+
'not_good.zsh',
431+
'very_good.zsh',
432+
'zsh-config.zsh',
433+
]),
434+
);
435+
436+
final bashInstallation = ShellCompletionInstallation(
437+
logger: logger,
438+
isWindows: false,
439+
environment: {
440+
'HOME': tempDir.path,
441+
},
442+
configuration: bashConfiguration,
443+
);
444+
445+
final bashProfile = File(path.join(tempDir.path, '.bash_profile'))
446+
..createSync();
447+
448+
bashInstallation
449+
..install('very_good')
450+
..install('not_good');
451+
452+
// ignore: leading_newlines_in_multiline_strings
453+
expect(bashProfile.readAsStringSync(), '''## [Completion]
454+
## Completion scripts setup. Remove the following line to uninstall
455+
[ -f ${configDir.path}/bash-config.bash ] && . ${configDir.path}/bash-config.bash || true
456+
## [/Completion]
457+
458+
''');
459+
460+
expect(
461+
configDir.listSync().map((e) => path.basename(e.path)),
462+
unorderedEquals([
463+
'not_good.bash',
464+
'not_good.zsh',
465+
'very_good.bash',
466+
'very_good.zsh',
467+
'zsh-config.zsh',
468+
'bash-config.bash'
469+
]),
470+
);
409471
},
410472
);
411473
});

0 commit comments

Comments
 (0)