Skip to content

Commit 7675b26

Browse files
authored
feat: include UnistallCompletionFilesCommand (#72)
1 parent 7b1726b commit 7675b26

File tree

9 files changed

+211
-27
lines changed

9 files changed

+211
-27
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
export 'handle_completion_command.dart';
22
export 'install_completion_files_command.dart';
3+
export 'uninstall_completion_files_command.dart';
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import 'dart:async';
2+
3+
import 'package:args/command_runner.dart';
4+
import 'package:cli_completion/cli_completion.dart';
5+
import 'package:mason_logger/mason_logger.dart';
6+
7+
/// {@template ninstall_completion_command}
8+
/// A hidden [Command] added by [CompletionCommandRunner] that handles the
9+
/// "uninstall-completion-files" sub command.
10+
///
11+
/// It can be used to manually uninstall the completion files
12+
/// (those installed by [CompletionCommandRunner] or
13+
/// [InstallCompletionFilesCommand]).
14+
/// {@endtemplate}
15+
class UnistallCompletionFilesCommand<T> extends Command<T> {
16+
/// {@macro uninstall_completion_command}
17+
UnistallCompletionFilesCommand() {
18+
argParser.addFlag(
19+
'verbose',
20+
abbr: 'v',
21+
help: 'Verbose output',
22+
negatable: false,
23+
);
24+
}
25+
26+
@override
27+
String get description {
28+
return 'Manually uninstalls completion files for the current shell.';
29+
}
30+
31+
/// The string that the user can call to manually uninstall completion files.
32+
static const commandName = 'uninstall-completion-files';
33+
34+
@override
35+
String get name => commandName;
36+
37+
@override
38+
bool get hidden => true;
39+
40+
@override
41+
CompletionCommandRunner<T> get runner {
42+
return super.runner! as CompletionCommandRunner<T>;
43+
}
44+
45+
@override
46+
FutureOr<T>? run() {
47+
final verbose = argResults!['verbose'] as bool;
48+
final level = verbose ? Level.verbose : Level.info;
49+
runner.tryUninstallCompletionFiles(level);
50+
return null;
51+
}
52+
}

lib/src/command_runner/completion_command_runner.dart

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ abstract class CompletionCommandRunner<T> extends CommandRunner<T> {
2525
CompletionCommandRunner(super.executableName, super.description) {
2626
addCommand(HandleCompletionRequestCommand<T>());
2727
addCommand(InstallCompletionFilesCommand<T>());
28+
addCommand(UnistallCompletionFilesCommand<T>());
2829
}
2930

3031
/// The [Logger] used to prompt the completion suggestions.
@@ -94,6 +95,19 @@ abstract class CompletionCommandRunner<T> extends CommandRunner<T> {
9495
}
9596
}
9697

98+
/// Tries to uninstall completion files for the current shell.
99+
@internal
100+
void tryUninstallCompletionFiles(Level level) {
101+
try {
102+
completionInstallationLogger.level = level;
103+
completionInstallation.uninstall(executableName);
104+
} on CompletionUninstallationException catch (e) {
105+
completionInstallationLogger.warn(e.toString());
106+
} on Exception catch (e) {
107+
completionInstallationLogger.err(e.toString());
108+
}
109+
}
110+
97111
/// Renders a [CompletionResult] into the current system shell.
98112
///
99113
/// This is called after a completion request (sent by a shell function) is

lib/src/installer/completion_installation.dart

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -315,7 +315,7 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';
315315
/// [ScriptConfigurationEntry] for the [rootCommand].
316316
///
317317
/// If any of the above is not true, it throws a
318-
/// [CompletionUnistallationException].
318+
/// [CompletionUninstallationException].
319319
///
320320
/// Upon a successful uninstallation the executable [ScriptConfigurationEntry]
321321
/// is removed from the shell configuration file. If after this removal the
@@ -332,16 +332,16 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';
332332

333333
final shellRCFile = File(_shellRCFilePath);
334334
if (!shellRCFile.existsSync()) {
335-
throw CompletionUnistallationException(
336-
executableName: rootCommand,
335+
throw CompletionUninstallationException(
336+
rootCommand: rootCommand,
337337
message: 'No shell RC file found at ${shellRCFile.path}',
338338
);
339339
}
340340

341341
const completionEntry = ScriptConfigurationEntry('Completion');
342342
if (!completionEntry.existsIn(shellRCFile)) {
343-
throw CompletionUnistallationException(
344-
executableName: rootCommand,
343+
throw CompletionUninstallationException(
344+
rootCommand: rootCommand,
345345
message: 'Completion is not installed at ${shellRCFile.path}',
346346
);
347347
}
@@ -354,8 +354,8 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';
354354
);
355355
final executableEntry = ScriptConfigurationEntry(rootCommand);
356356
if (!executableEntry.existsIn(shellCompletionConfigurationFile)) {
357-
throw CompletionUnistallationException(
358-
executableName: rootCommand,
357+
throw CompletionUninstallationException(
358+
rootCommand: rootCommand,
359359
message:
360360
'''No shell script file found at ${shellCompletionConfigurationFile.path}''',
361361
);

lib/src/installer/exceptions.dart

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,20 +22,20 @@ class CompletionInstallationException implements Exception {
2222
/// {@template completion_unistallation_exception}
2323
/// Describes an exception during the uninstallation of completion scripts.
2424
/// {@endtemplate}
25-
class CompletionUnistallationException implements Exception {
25+
class CompletionUninstallationException implements Exception {
2626
/// {@macro completion_unistallation_exception}
27-
CompletionUnistallationException({
27+
CompletionUninstallationException({
2828
required this.message,
29-
required this.executableName,
29+
required this.rootCommand,
3030
});
3131

3232
/// The error message for this exception
3333
final String message;
3434

3535
/// The command for which the installation failed.
36-
final String executableName;
36+
final String rootCommand;
3737

3838
@override
3939
String toString() =>
40-
'''Could not uninstall completion scripts for $executableName: $message''';
40+
'''Could not uninstall completion scripts for $rootCommand: $message''';
4141
}
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import 'package:cli_completion/cli_completion.dart';
2+
import 'package:cli_completion/installer.dart';
3+
import 'package:mason_logger/mason_logger.dart';
4+
import 'package:mocktail/mocktail.dart';
5+
import 'package:test/test.dart';
6+
7+
class _MockLogger extends Mock implements Logger {}
8+
9+
class _MockCompletionInstallation extends Mock
10+
implements CompletionInstallation {}
11+
12+
class _TestCompletionCommandRunner extends CompletionCommandRunner<int> {
13+
_TestCompletionCommandRunner() : super('test', 'Test command runner');
14+
15+
@override
16+
// ignore: overridden_fields
17+
final Logger completionInstallationLogger = _MockLogger();
18+
19+
@override
20+
final CompletionInstallation completionInstallation =
21+
_MockCompletionInstallation();
22+
}
23+
24+
void main() {
25+
group('$UnistallCompletionFilesCommand', () {
26+
late _TestCompletionCommandRunner commandRunner;
27+
28+
setUp(() {
29+
commandRunner = _TestCompletionCommandRunner();
30+
});
31+
32+
test('can be instantiated', () {
33+
expect(UnistallCompletionFilesCommand<int>(), isNotNull);
34+
});
35+
36+
test('is hidden', () {
37+
expect(UnistallCompletionFilesCommand<int>().hidden, isTrue);
38+
});
39+
40+
test('description', () {
41+
expect(
42+
UnistallCompletionFilesCommand<int>().description,
43+
'Manually uninstalls completion files for the current shell.',
44+
);
45+
});
46+
47+
group('uninstalls completion files', () {
48+
test('when normal', () async {
49+
await commandRunner.run(['uninstall-completion-files']);
50+
51+
verify(
52+
() => commandRunner.completionInstallationLogger.level = Level.info,
53+
).called(1);
54+
verify(
55+
() => commandRunner.completionInstallation
56+
.uninstall(commandRunner.executableName),
57+
).called(1);
58+
});
59+
60+
test('when verbose', () async {
61+
await commandRunner.run(['uninstall-completion-files', '--verbose']);
62+
63+
verify(
64+
() {
65+
return commandRunner.completionInstallationLogger.level =
66+
Level.verbose;
67+
},
68+
).called(1);
69+
verify(
70+
() => commandRunner.completionInstallation
71+
.uninstall(commandRunner.executableName),
72+
).called(1);
73+
});
74+
});
75+
});
76+
}

test/src/command_runner/completion_command_runner_test.dart

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,47 @@ void main() {
180180
.called(1);
181181
});
182182

183+
group('tryUninstallCompletionFiles', () {
184+
test(
185+
'logs a warning wen it throws $CompletionUninstallationException',
186+
() async {
187+
final commandRunner = _TestCompletionCommandRunner()
188+
..mockCompletionInstallation = MockCompletionInstallation();
189+
190+
when(
191+
() => commandRunner.completionInstallation.uninstall('test'),
192+
).thenThrow(
193+
CompletionUninstallationException(
194+
message: 'oops',
195+
rootCommand: 'test',
196+
),
197+
);
198+
199+
commandRunner.tryUninstallCompletionFiles(Level.verbose);
200+
201+
verify(() => commandRunner.completionInstallationLogger.warn(any()))
202+
.called(1);
203+
},
204+
);
205+
206+
test(
207+
'logs an error when an unknown exception happens during a install',
208+
() async {
209+
final commandRunner = _TestCompletionCommandRunner()
210+
..mockCompletionInstallation = MockCompletionInstallation();
211+
212+
when(
213+
() => commandRunner.completionInstallation.uninstall('test'),
214+
).thenThrow(Exception('oops'));
215+
216+
commandRunner.tryUninstallCompletionFiles(Level.verbose);
217+
218+
verify(() => commandRunner.completionInstallationLogger.err(any()))
219+
.called(1);
220+
},
221+
);
222+
});
223+
183224
group('renderCompletionResult', () {
184225
test('renders predefined suggestions on zsh', () {
185226
const completionResult = _TestCompletionResult({

test/src/installer/completion_installation_test.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -750,7 +750,7 @@ void main() {
750750
expect(
751751
() => installation.uninstall('very_good'),
752752
throwsA(
753-
isA<CompletionUnistallationException>().having(
753+
isA<CompletionUninstallationException>().having(
754754
(e) => e.message,
755755
'message',
756756
equals('No shell RC file found at ${rcFile.path}'),
@@ -778,7 +778,7 @@ void main() {
778778
expect(
779779
() => installation.uninstall('very_good'),
780780
throwsA(
781-
isA<CompletionUnistallationException>().having(
781+
isA<CompletionUninstallationException>().having(
782782
(e) => e.message,
783783
'message',
784784
equals('Completion is not installed at ${rcFile.path}'),
@@ -815,7 +815,7 @@ void main() {
815815
expect(
816816
() => installation.uninstall('very_good'),
817817
throwsA(
818-
isA<CompletionUnistallationException>().having(
818+
isA<CompletionUninstallationException>().having(
819819
(e) => e.message,
820820
'message',
821821
equals(

test/src/installer/exceptions_test.dart

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,53 +2,53 @@ import 'package:cli_completion/installer.dart';
22
import 'package:test/test.dart';
33

44
void main() {
5-
group('$CompletionUnistallationException', () {
5+
group('$CompletionUninstallationException', () {
66
test('can be instantiated', () {
77
expect(
8-
() => CompletionUnistallationException(
8+
() => CompletionUninstallationException(
99
message: 'message',
10-
executableName: 'executableName',
10+
rootCommand: 'executableName',
1111
),
1212
returnsNormally,
1313
);
1414
});
1515

1616
test('has a message', () {
1717
expect(
18-
CompletionUnistallationException(
18+
CompletionUninstallationException(
1919
message: 'message',
20-
executableName: 'executableName',
20+
rootCommand: 'executableName',
2121
).message,
2222
equals('message'),
2323
);
2424
});
2525

2626
test('has an executableName', () {
2727
expect(
28-
CompletionUnistallationException(
28+
CompletionUninstallationException(
2929
message: 'message',
30-
executableName: 'executableName',
31-
).executableName,
30+
rootCommand: 'executableName',
31+
).rootCommand,
3232
equals('executableName'),
3333
);
3434
});
3535

3636
group('toString', () {
3737
test('returns a string', () {
3838
expect(
39-
CompletionUnistallationException(
39+
CompletionUninstallationException(
4040
message: 'message',
41-
executableName: 'executableName',
41+
rootCommand: 'executableName',
4242
).toString(),
4343
isA<String>(),
4444
);
4545
});
4646

4747
test('returns a correctly formatted string', () {
4848
expect(
49-
CompletionUnistallationException(
49+
CompletionUninstallationException(
5050
message: 'message',
51-
executableName: 'executableName',
51+
rootCommand: 'executableName',
5252
).toString(),
5353
equals(
5454
'''Could not uninstall completion scripts for executableName: message''',

0 commit comments

Comments
 (0)