Skip to content

Commit

Permalink
feat: include uninstallation logic (#70)
Browse files Browse the repository at this point in the history
Co-authored-by: Renan <6718144+renancaraujo@users.noreply.github.com>
  • Loading branch information
alestiago and renancaraujo authored May 17, 2023
1 parent a9e21de commit 7b1726b
Show file tree
Hide file tree
Showing 6 changed files with 666 additions and 8 deletions.
79 changes: 79 additions & 0 deletions lib/src/installer/completion_installation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,85 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';

logger.info('Added config to $configFilePath');
}

/// Uninstalls the completion for the command [rootCommand] on the current
/// shell.
///
/// Before uninstalling, it checks if the completion is installed:
/// - The shell has an existing RCFile with a completion
/// [ScriptConfigurationEntry].
/// - The shell has an existing completion configuration file with a
/// [ScriptConfigurationEntry] for the [rootCommand].
///
/// If any of the above is not true, it throws a
/// [CompletionUnistallationException].
///
/// Upon a successful uninstallation the executable [ScriptConfigurationEntry]
/// is removed from the shell configuration file. If after this removal the
/// latter is empty, it is deleted together with the the executable completion
/// script and the completion [ScriptConfigurationEntry] from the shell RC
/// file. In the case that there are no other completion scripts installed on
/// other shells the completion config directory is deleted, leaving the
/// user's system as it was before the installation.
void uninstall(String rootCommand) {
final configuration = this.configuration!;
logger.detail(
'''Uninstalling completion for the command $rootCommand on ${configuration.shell.name}''',
);

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

const completionEntry = ScriptConfigurationEntry('Completion');
if (!completionEntry.existsIn(shellRCFile)) {
throw CompletionUnistallationException(
executableName: rootCommand,
message: 'Completion is not installed at ${shellRCFile.path}',
);
}

final shellCompletionConfigurationFile = File(
path.join(
completionConfigDir.path,
configuration.completionConfigForShellFileName,
),
);
final executableEntry = ScriptConfigurationEntry(rootCommand);
if (!executableEntry.existsIn(shellCompletionConfigurationFile)) {
throw CompletionUnistallationException(
executableName: rootCommand,
message:
'''No shell script file found at ${shellCompletionConfigurationFile.path}''',
);
}

final executableShellCompletionScriptFile = File(
path.join(
completionConfigDir.path,
'$rootCommand.${configuration.shell.name}',
),
);
if (executableShellCompletionScriptFile.existsSync()) {
executableShellCompletionScriptFile.deleteSync();
}

executableEntry.removeFrom(
shellCompletionConfigurationFile,
shouldDelete: true,
);
if (!shellCompletionConfigurationFile.existsSync()) {
completionEntry.removeFrom(shellRCFile);
}

if (completionConfigDir.listSync().isEmpty) {
completionConfigDir.deleteSync();
}
}
}

/// Resolve the home from a path string
Expand Down
21 changes: 21 additions & 0 deletions lib/src/installer/exceptions.dart
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,24 @@ class CompletionInstallationException implements Exception {
String toString() => 'Could not install completion scripts for $rootCommand: '
'$message';
}

/// {@template completion_unistallation_exception}
/// Describes an exception during the uninstallation of completion scripts.
/// {@endtemplate}
class CompletionUnistallationException implements Exception {
/// {@macro completion_unistallation_exception}
CompletionUnistallationException({
required this.message,
required this.executableName,
});

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

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

@override
String toString() =>
'''Could not uninstall completion scripts for $executableName: $message''';
}
38 changes: 34 additions & 4 deletions lib/src/installer/script_configuration_entry.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,16 +38,46 @@ class ScriptConfigurationEntry {
file.createSync(recursive: true);
}

final entry = StringBuffer()
final stringBuffer = StringBuffer()
..writeln()
..writeln(_startComment)
..writeln(content)
..writeln(_startComment);
if (content != null) stringBuffer.writeln(content);
stringBuffer
..writeln(_endComment)
..writeln();

file.writeAsStringSync(
entry.toString(),
stringBuffer.toString(),
mode: FileMode.append,
);
}

/// Removes the entry with [name] from the [file].
///
/// If the [file] does not exist, this will do nothing.
///
/// If a file has multiple entries with the same [name], all of them will be
/// removed.
///
/// If [shouldDelete] is true, the [file] will be deleted if it is empty after
/// removing the entry. Otherwise, the [file] will be left empty.
void removeFrom(File file, {bool shouldDelete = false}) {
if (!file.existsSync()) return;

final content = file.readAsStringSync();
final stringPattern = '\n$_startComment.*$_endComment\n\n'
.replaceAll('[', r'\[')
.replaceAll(']', r'\]');
final pattern = RegExp(
stringPattern,
multiLine: true,
dotAll: true,
);
final newContent = content.replaceAllMapped(pattern, (_) => '');
file.writeAsStringSync(newContent);

if (shouldDelete && newContent.trim().isEmpty) {
file.deleteSync();
}
}
}
Loading

0 comments on commit 7b1726b

Please sign in to comment.