Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: include UnistallCompletionFilesCommand #72

Merged
merged 37 commits into from
May 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
12eb748
refactor: added ScriptConfigurationEntry
alestiago May 10, 2023
000aa81
refactor: remove empty space
alestiago May 10, 2023
ac0a310
refactor: use completion installation to verify installation
alestiago May 10, 2023
4421964
refactor: removing unused code
alestiago May 10, 2023
8e6939c
feat: reformatted start and end comments
alestiago May 10, 2023
3397c16
refactor: used barrel file
alestiago May 10, 2023
6d37050
test: refactor expectation
alestiago May 10, 2023
c9cca0a
test: remove white space
alestiago May 11, 2023
ba6eaaf
Merge branch 'main' into refactor/script-configuration-entry
alestiago May 11, 2023
ac810c3
feat: fixed merge issue
alestiago May 11, 2023
98f4cf0
test: removed extra space
alestiago May 11, 2023
ccf8de3
feat: uninstall logic
alestiago May 11, 2023
111892c
Merge branch 'main' into refactor/uninstallation-logic
alestiago May 12, 2023
fd16cfa
test: removeFrom
alestiago May 12, 2023
2b19a14
fix: added break condition
alestiago May 12, 2023
817ae1b
refactor: used while instead of do while
alestiago May 12, 2023
c87eb66
feat: refined removedFrom
alestiago May 13, 2023
b571c14
feat: refined uninstall logic
alestiago May 13, 2023
e8335c9
feat: tested and configured uninstalling behaviour
alestiago May 13, 2023
c327c2c
test: included missing executable script deletion
alestiago May 13, 2023
bfccc39
feat: refined uninstallation logic
alestiago May 13, 2023
11b990e
feat: improved uninstall behaviour for multiple shells
alestiago May 13, 2023
b2db32a
test: added CompletionUnistallationException
alestiago May 13, 2023
ac3a066
docs: improved uninstall documentation
alestiago May 13, 2023
55ee7ec
test: improved names
alestiago May 13, 2023
e249a97
refactor: renamed executablName to rootCommand
alestiago May 16, 2023
96b2796
refactor: made shouldDelete false by default
alestiago May 16, 2023
db0b752
docs: fixed wrong documentation
alestiago May 16, 2023
cba7174
test: renamed file
alestiago May 16, 2023
0736ca5
test: include _test in test name
alestiago May 16, 2023
68114b5
feat: added UnistallCompletionFilesCommand
alestiago May 17, 2023
5bff068
test: included uninstallation test
alestiago May 17, 2023
f1d4fce
refactor: removed unused import
alestiago May 17, 2023
a609970
Merge branch 'main' into feat/uninstall-command
alestiago May 17, 2023
aa10217
feat: added generic
alestiago May 17, 2023
d9f7773
test: reached coverage
alestiago May 18, 2023
117a2b3
refactor: CompletionUninstallationException typo
alestiago May 18, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/src/command_runner/commands/commands.dart
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export 'handle_completion_command.dart';
export 'install_completion_files_command.dart';
export 'uninstall_completion_files_command.dart';
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import 'dart:async';

import 'package:args/command_runner.dart';
import 'package:cli_completion/cli_completion.dart';
import 'package:mason_logger/mason_logger.dart';

/// {@template ninstall_completion_command}
/// A hidden [Command] added by [CompletionCommandRunner] that handles the
/// "uninstall-completion-files" sub command.
///
/// It can be used to manually uninstall the completion files
/// (those installed by [CompletionCommandRunner] or
/// [InstallCompletionFilesCommand]).
/// {@endtemplate}
class UnistallCompletionFilesCommand<T> extends Command<T> {
/// {@macro uninstall_completion_command}
UnistallCompletionFilesCommand() {
argParser.addFlag(
'verbose',
abbr: 'v',
help: 'Verbose output',
negatable: false,
);
}

@override
String get description {
return 'Manually uninstalls completion files for the current shell.';
}

/// The string that the user can call to manually uninstall completion files.
static const commandName = 'uninstall-completion-files';

@override
String get name => commandName;

@override
bool get hidden => true;

@override
CompletionCommandRunner<T> get runner {
return super.runner! as CompletionCommandRunner<T>;
}

@override
FutureOr<T>? run() {
final verbose = argResults!['verbose'] as bool;
final level = verbose ? Level.verbose : Level.info;
runner.tryUninstallCompletionFiles(level);
return null;
}
}
14 changes: 14 additions & 0 deletions lib/src/command_runner/completion_command_runner.dart
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ abstract class CompletionCommandRunner<T> extends CommandRunner<T> {
CompletionCommandRunner(super.executableName, super.description) {
addCommand(HandleCompletionRequestCommand<T>());
addCommand(InstallCompletionFilesCommand<T>());
addCommand(UnistallCompletionFilesCommand<T>());
}

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

/// Tries to uninstall completion files for the current shell.
@internal
void tryUninstallCompletionFiles(Level level) {
try {
completionInstallationLogger.level = level;
completionInstallation.uninstall(executableName);
} on CompletionUninstallationException catch (e) {
completionInstallationLogger.warn(e.toString());
} on Exception catch (e) {
completionInstallationLogger.err(e.toString());
}
}

/// Renders a [CompletionResult] into the current system shell.
///
/// This is called after a completion request (sent by a shell function) is
Expand Down
14 changes: 7 additions & 7 deletions lib/src/installer/completion_installation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -315,7 +315,7 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';
/// [ScriptConfigurationEntry] for the [rootCommand].
///
/// If any of the above is not true, it throws a
/// [CompletionUnistallationException].
/// [CompletionUninstallationException].
///
/// Upon a successful uninstallation the executable [ScriptConfigurationEntry]
/// is removed from the shell configuration file. If after this removal the
Expand All @@ -332,16 +332,16 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';

final shellRCFile = File(_shellRCFilePath);
if (!shellRCFile.existsSync()) {
throw CompletionUnistallationException(
executableName: rootCommand,
throw CompletionUninstallationException(
rootCommand: rootCommand,
message: 'No shell RC file found at ${shellRCFile.path}',
);
}

const completionEntry = ScriptConfigurationEntry('Completion');
if (!completionEntry.existsIn(shellRCFile)) {
throw CompletionUnistallationException(
executableName: rootCommand,
throw CompletionUninstallationException(
rootCommand: rootCommand,
message: 'Completion is not installed at ${shellRCFile.path}',
);
}
Expand All @@ -354,8 +354,8 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';
);
final executableEntry = ScriptConfigurationEntry(rootCommand);
if (!executableEntry.existsIn(shellCompletionConfigurationFile)) {
throw CompletionUnistallationException(
executableName: rootCommand,
throw CompletionUninstallationException(
rootCommand: rootCommand,
message:
'''No shell script file found at ${shellCompletionConfigurationFile.path}''',
);
Expand Down
10 changes: 5 additions & 5 deletions lib/src/installer/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,20 +22,20 @@ class CompletionInstallationException implements Exception {
/// {@template completion_unistallation_exception}
/// Describes an exception during the uninstallation of completion scripts.
/// {@endtemplate}
class CompletionUnistallationException implements Exception {
class CompletionUninstallationException implements Exception {
/// {@macro completion_unistallation_exception}
CompletionUnistallationException({
CompletionUninstallationException({
required this.message,
required this.executableName,
required this.rootCommand,
});

/// The error message for this exception
final String message;

/// The command for which the installation failed.
final String executableName;
final String rootCommand;

@override
String toString() =>
'''Could not uninstall completion scripts for $executableName: $message''';
'''Could not uninstall completion scripts for $rootCommand: $message''';
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import 'package:cli_completion/cli_completion.dart';
import 'package:cli_completion/installer.dart';
import 'package:mason_logger/mason_logger.dart';
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

class _MockLogger extends Mock implements Logger {}

class _MockCompletionInstallation extends Mock
implements CompletionInstallation {}

class _TestCompletionCommandRunner extends CompletionCommandRunner<int> {
_TestCompletionCommandRunner() : super('test', 'Test command runner');

@override
// ignore: overridden_fields
final Logger completionInstallationLogger = _MockLogger();

@override
final CompletionInstallation completionInstallation =
_MockCompletionInstallation();
}

void main() {
group('$UnistallCompletionFilesCommand', () {
late _TestCompletionCommandRunner commandRunner;

setUp(() {
commandRunner = _TestCompletionCommandRunner();
});

test('can be instantiated', () {
expect(UnistallCompletionFilesCommand<int>(), isNotNull);
});

test('is hidden', () {
expect(UnistallCompletionFilesCommand<int>().hidden, isTrue);
});

test('description', () {
expect(
UnistallCompletionFilesCommand<int>().description,
'Manually uninstalls completion files for the current shell.',
);
});

group('uninstalls completion files', () {
test('when normal', () async {
await commandRunner.run(['uninstall-completion-files']);

verify(
() => commandRunner.completionInstallationLogger.level = Level.info,
).called(1);
verify(
() => commandRunner.completionInstallation
.uninstall(commandRunner.executableName),
).called(1);
});

test('when verbose', () async {
await commandRunner.run(['uninstall-completion-files', '--verbose']);

verify(
() {
return commandRunner.completionInstallationLogger.level =
Level.verbose;
},
).called(1);
verify(
() => commandRunner.completionInstallation
.uninstall(commandRunner.executableName),
).called(1);
});
});
});
}
41 changes: 41 additions & 0 deletions test/src/command_runner/completion_command_runner_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,47 @@ void main() {
.called(1);
});

group('tryUninstallCompletionFiles', () {
test(
'logs a warning wen it throws $CompletionUninstallationException',
() async {
final commandRunner = _TestCompletionCommandRunner()
..mockCompletionInstallation = MockCompletionInstallation();

when(
() => commandRunner.completionInstallation.uninstall('test'),
).thenThrow(
CompletionUninstallationException(
message: 'oops',
rootCommand: 'test',
),
);

commandRunner.tryUninstallCompletionFiles(Level.verbose);

verify(() => commandRunner.completionInstallationLogger.warn(any()))
.called(1);
},
);

test(
'logs an error when an unknown exception happens during a install',
() async {
final commandRunner = _TestCompletionCommandRunner()
..mockCompletionInstallation = MockCompletionInstallation();

when(
() => commandRunner.completionInstallation.uninstall('test'),
).thenThrow(Exception('oops'));

commandRunner.tryUninstallCompletionFiles(Level.verbose);

verify(() => commandRunner.completionInstallationLogger.err(any()))
.called(1);
},
);
});

group('renderCompletionResult', () {
test('renders predefined suggestions on zsh', () {
const completionResult = _TestCompletionResult({
Expand Down
6 changes: 3 additions & 3 deletions test/src/installer/completion_installation_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -750,7 +750,7 @@ void main() {
expect(
() => installation.uninstall('very_good'),
throwsA(
isA<CompletionUnistallationException>().having(
isA<CompletionUninstallationException>().having(
(e) => e.message,
'message',
equals('No shell RC file found at ${rcFile.path}'),
Expand Down Expand Up @@ -778,7 +778,7 @@ void main() {
expect(
() => installation.uninstall('very_good'),
throwsA(
isA<CompletionUnistallationException>().having(
isA<CompletionUninstallationException>().having(
(e) => e.message,
'message',
equals('Completion is not installed at ${rcFile.path}'),
Expand Down Expand Up @@ -815,7 +815,7 @@ void main() {
expect(
() => installation.uninstall('very_good'),
throwsA(
isA<CompletionUnistallationException>().having(
isA<CompletionUninstallationException>().having(
(e) => e.message,
'message',
equals(
Expand Down
24 changes: 12 additions & 12 deletions test/src/installer/exceptions_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,53 +2,53 @@ import 'package:cli_completion/installer.dart';
import 'package:test/test.dart';

void main() {
group('$CompletionUnistallationException', () {
group('$CompletionUninstallationException', () {
test('can be instantiated', () {
expect(
() => CompletionUnistallationException(
() => CompletionUninstallationException(
message: 'message',
executableName: 'executableName',
rootCommand: 'executableName',
),
returnsNormally,
);
});

test('has a message', () {
expect(
CompletionUnistallationException(
CompletionUninstallationException(
message: 'message',
executableName: 'executableName',
rootCommand: 'executableName',
).message,
equals('message'),
);
});

test('has an executableName', () {
expect(
CompletionUnistallationException(
CompletionUninstallationException(
message: 'message',
executableName: 'executableName',
).executableName,
rootCommand: 'executableName',
).rootCommand,
equals('executableName'),
);
});

group('toString', () {
test('returns a string', () {
expect(
CompletionUnistallationException(
CompletionUninstallationException(
message: 'message',
executableName: 'executableName',
rootCommand: 'executableName',
).toString(),
isA<String>(),
);
});

test('returns a correctly formatted string', () {
expect(
CompletionUnistallationException(
CompletionUninstallationException(
message: 'message',
executableName: 'executableName',
rootCommand: 'executableName',
).toString(),
equals(
'''Could not uninstall completion scripts for executableName: message''',
Expand Down