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: avoid auto-installing when already installed #74

Merged
merged 47 commits into from
May 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
6ce5d9a
feat: added logic
alestiago May 18, 2023
e4ae10c
refactor: removed empty line
alestiago May 18, 2023
d9e1100
refactor: formatting
alestiago May 18, 2023
677f897
refactor: refined autoinstallation logic
alestiago May 18, 2023
0d41fda
fix: resolved uninstallation directory removal logic
alestiago May 18, 2023
1f3cd35
test: added 'config.json' to files
alestiago May 18, 2023
08e2b72
test: fixed unmocked tests
alestiago May 18, 2023
6348750
test: added unmocked instances
alestiago May 18, 2023
43180f8
test: added TODOs
alestiago May 18, 2023
30e7cc0
refactor: formatter
alestiago May 18, 2023
adc1684
test: added UninstallsExtension tests
alestiago May 22, 2023
b99b5bd
refactor: fixed formatter
alestiago May 22, 2023
5525151
feat: added new command as reserved
alestiago May 22, 2023
86aee3b
test: don't auto install when uninstalled
alestiago May 22, 2023
82a6cc3
test: added installation tests
alestiago May 22, 2023
434fe91
test: does not auto install when it is unistalled
alestiago May 22, 2023
e657265
test: used executableName
alestiago May 22, 2023
b2422ec
test: used command runner shell
alestiago May 22, 2023
261aa3f
test: defined environment override for shell
alestiago May 22, 2023
1d9da79
feat: support for caching installs
alestiago May 23, 2023
b9adf05
test: added completion_installation tests
alestiago May 23, 2023
0ea3d2f
test: added completion_command_runner test
alestiago May 23, 2023
edc4af9
feat: efficiently check for autoinstall before reading cache
alestiago May 23, 2023
71a97db
refactor: simplfied auto-installation logic
alestiago May 23, 2023
bc6201b
refactor: made reserved commands static
alestiago May 23, 2023
0577b73
refactor: made reserved commands const
alestiago May 23, 2023
cddebda
refactor: analysis
alestiago May 23, 2023
6397ee0
refactor: rename `Uninstalls` to `ShellCommandsMap` (#75)
alestiago May 23, 2023
a375d99
refactor: moved logic to install
alestiago May 23, 2023
b2dfa4b
refactor: removed errors
alestiago May 23, 2023
366a480
docs: fixed docs
alestiago May 23, 2023
2253015
refactor: modify logic
alestiago May 23, 2023
0a6b21f
test: soft install
alestiago May 23, 2023
ab652bf
docs: fixed docs
alestiago May 23, 2023
c741dd3
refactor: removed mocks
alestiago May 23, 2023
135e0d1
refactor: test name
alestiago May 23, 2023
39128a1
test: removed redundant variable
alestiago May 23, 2023
15f305f
test: simplified
alestiago May 23, 2023
190338f
test: small typo
alestiago May 23, 2023
6f4fafb
Merge branch 'feat/avoid-autoinstalling-uninstalls' into feat/efficie…
alestiago May 23, 2023
f0fbaac
feat: added installation logic
alestiago May 23, 2023
2944d43
docs: typo
alestiago May 23, 2023
ce17d6f
Merge branch 'main' into feat/efficient-auto-installs
alestiago May 24, 2023
d8eae5f
fix: renamed merges
alestiago May 24, 2023
743e6d6
refactor: removed repeated tests
alestiago May 24, 2023
1fd1d43
test: ammended tests
alestiago May 24, 2023
b8ae8ab
refactor: renamed tests to "remains the same"
alestiago May 24, 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
103 changes: 65 additions & 38 deletions lib/src/installer/completion_configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import 'package:meta/meta.dart';
///
/// The map and its content are unmodifiable. This is to ensure that
/// [CompletionConfiguration]s is fully immutable.
typedef Uninstalls
typedef ShellCommandsMap
= UnmodifiableMapView<SystemShell, UnmodifiableSetView<String>>;

/// {@template completion_configuration}
Expand All @@ -22,11 +22,14 @@ class CompletionConfiguration {
/// {@macro completion_configuration}
const CompletionConfiguration._({
required this.uninstalls,
required this.installs,
});

/// Creates an empty [CompletionConfiguration].
@visibleForTesting
CompletionConfiguration.empty() : uninstalls = UnmodifiableMapView({});
CompletionConfiguration.empty()
: uninstalls = ShellCommandsMap({}),
installs = ShellCommandsMap({});

/// Creates a [CompletionConfiguration] from the given [file] content.
///
Expand Down Expand Up @@ -56,17 +59,34 @@ class CompletionConfiguration {
}

return CompletionConfiguration._(
uninstalls: _jsonDecodeUninstalls(decodedJson),
uninstalls: _jsonDecodeShellCommandsMap(
decodedJson,
jsonKey: CompletionConfiguration.uninstallsJsonKey,
),
installs: _jsonDecodeShellCommandsMap(
decodedJson,
jsonKey: CompletionConfiguration.installsJsonKey,
),
);
}

/// The JSON key for the [uninstalls] field.
static const String _uninstallsJsonKey = 'uninstalls';
@visibleForTesting
static const String uninstallsJsonKey = 'uninstalls';

/// The JSON key for the [installs] field.
@visibleForTesting
static const String installsJsonKey = 'installs';

/// Stores those commands that have been manually uninstalled by the user.
///
/// Uninstalls are specific to a given [SystemShell].
final Uninstalls uninstalls;
final ShellCommandsMap uninstalls;

/// Stores those commands that have completion installed.
///
/// Installed commands are specific to a given [SystemShell].
final ShellCommandsMap installs;

/// Stores the [CompletionConfiguration] in the given [file].
void writeTo(File file) {
Expand All @@ -76,73 +96,80 @@ class CompletionConfiguration {
file.writeAsStringSync(_toJson());
}

/// Returns a JSON representation of this [CompletionConfiguration].
String _toJson() {
return jsonEncode({
uninstallsJsonKey: _jsonEncodeShellCommandsMap(uninstalls),
installsJsonKey: _jsonEncodeShellCommandsMap(installs),
});
}

/// Returns a copy of this [CompletionConfiguration] with the given fields
/// replaced.
CompletionConfiguration copyWith({
Uninstalls? uninstalls,
ShellCommandsMap? uninstalls,
ShellCommandsMap? installs,
}) {
return CompletionConfiguration._(
uninstalls: uninstalls ?? this.uninstalls,
installs: installs ?? this.installs,
);
}

/// Returns a JSON representation of this [CompletionConfiguration].
String _toJson() {
return jsonEncode({
_uninstallsJsonKey: _jsonEncodeUninstalls(uninstalls),
});
}
}

/// Decodes [Uninstalls] from the given [json].
/// Decodes [ShellCommandsMap] from the given [json].
///
/// If the [json] is not partially or fully valid, it handles issues gracefully
/// without throwing an [Exception].
Uninstalls _jsonDecodeUninstalls(Map<String, dynamic> json) {
if (!json.containsKey(CompletionConfiguration._uninstallsJsonKey)) {
return UnmodifiableMapView({});
ShellCommandsMap _jsonDecodeShellCommandsMap(
Map<String, dynamic> json, {
required String jsonKey,
}) {
if (!json.containsKey(jsonKey)) {
return ShellCommandsMap({});
}
final jsonUninstalls = json[CompletionConfiguration._uninstallsJsonKey];
if (jsonUninstalls is! String) {
return UnmodifiableMapView({});
final jsonShellCommandsMap = json[jsonKey];
if (jsonShellCommandsMap is! String) {
return ShellCommandsMap({});
}
late final Map<String, dynamic> decodedUninstalls;
late final Map<String, dynamic> decodedShellCommandsMap;
try {
decodedUninstalls = jsonDecode(jsonUninstalls) as Map<String, dynamic>;
decodedShellCommandsMap =
jsonDecode(jsonShellCommandsMap) as Map<String, dynamic>;
} on FormatException {
return UnmodifiableMapView({});
return ShellCommandsMap({});
}

final newUninstalls = <SystemShell, UnmodifiableSetView<String>>{};
for (final entry in decodedUninstalls.entries) {
final newShellCommandsMap = <SystemShell, UnmodifiableSetView<String>>{};
for (final entry in decodedShellCommandsMap.entries) {
final systemShell = SystemShell.tryParse(entry.key);
if (systemShell == null) continue;
final uninstallSet = <String>{};
final commandsSet = <String>{};
if (entry.value is List) {
for (final uninstall in entry.value as List) {
if (uninstall is String) {
uninstallSet.add(uninstall);
commandsSet.add(uninstall);
}
}
}
newUninstalls[systemShell] = UnmodifiableSetView(uninstallSet);
newShellCommandsMap[systemShell] = UnmodifiableSetView(commandsSet);
}
return UnmodifiableMapView(newUninstalls);
return UnmodifiableMapView(newShellCommandsMap);
}

/// Returns a JSON representation of the given [Uninstalls].
String _jsonEncodeUninstalls(Uninstalls uninstalls) {
/// Returns a JSON representation of the given [ShellCommandsMap].
String _jsonEncodeShellCommandsMap(ShellCommandsMap shellCommandsMap) {
return jsonEncode({
for (final entry in uninstalls.entries)
for (final entry in shellCommandsMap.entries)
entry.key.toString(): entry.value.toList(),
});
}

/// Provides convinience methods for [Uninstalls].
extension UninstallsExtension on Uninstalls {
/// Returns a new [Uninstalls] with the given [command] added to
/// Provides convinience methods for [ShellCommandsMap].
extension ShellCommandsMapExtension on ShellCommandsMap {
/// Returns a new [ShellCommandsMap] with the given [command] added to
/// [systemShell].
Uninstalls include({
ShellCommandsMap include({
required String command,
required SystemShell systemShell,
}) {
Expand All @@ -159,9 +186,9 @@ extension UninstallsExtension on Uninstalls {
);
}

/// Returns a new [Uninstalls] with the given [command] removed from
/// Returns a new [ShellCommandsMap] with the given [command] removed from
/// [systemShell].
Uninstalls exclude({
ShellCommandsMap exclude({
required String command,
required SystemShell systemShell,
}) {
Expand Down
21 changes: 17 additions & 4 deletions lib/src/installer/completion_installation.dart
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,8 @@ class CompletionInstallation {
/// the aforementioned config file.
///
/// If [force] is true, it will overwrite the command's completion files even
/// if they already exist. If false, it will check if it has been explicitly
/// uninstalled before installing it.
/// if they already exist. If false, it will check if is already installed, or
/// if it has been explicitly uninstalled before installing it.
void install(String rootCommand, {bool force = false}) {
final configuration = this.configuration;

Expand Down Expand Up @@ -147,24 +147,33 @@ class CompletionInstallation {
command: rootCommand,
systemShell: configuration.shell,
),
installs: completionConfiguration.installs.include(
command: rootCommand,
systemShell: configuration.shell,
),
)
.writeTo(completionConfigurationFile);
}

/// Wether the completion configuration files for a [rootCommand] should be
/// installed or not.
///
/// It will return false if the root command has been explicitly uninstalled.
/// It will return false if the root command is already installed or it
/// has been explicitly uninstalled.
bool _shouldInstall(String rootCommand) {
final completionConfiguration = CompletionConfiguration.fromFile(
completionConfigurationFile,
);
final systemShell = configuration!.shell;
final isInstalled = completionConfiguration.installs.contains(
command: rootCommand,
systemShell: systemShell,
);
final isUninstalled = completionConfiguration.uninstalls.contains(
command: rootCommand,
systemShell: systemShell,
);
return !isUninstalled;
return !isInstalled && !isUninstalled;
}

/// Create a directory in which the completion config files shall be saved.
Expand Down Expand Up @@ -434,6 +443,10 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';
command: rootCommand,
systemShell: configuration.shell,
),
installs: completionConfiguration.installs.exclude(
command: rootCommand,
systemShell: configuration.shell,
),
)
.writeTo(completionConfigurationFile);
}
Expand Down
Loading