Skip to content

Commit 333371e

Browse files
authored
feat: avoid auto-installing when already installed (#74)
1 parent 527db4c commit 333371e

File tree

4 files changed

+405
-123
lines changed

4 files changed

+405
-123
lines changed

lib/src/installer/completion_configuration.dart

Lines changed: 65 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import 'package:meta/meta.dart';
1010
///
1111
/// The map and its content are unmodifiable. This is to ensure that
1212
/// [CompletionConfiguration]s is fully immutable.
13-
typedef Uninstalls
13+
typedef ShellCommandsMap
1414
= UnmodifiableMapView<SystemShell, UnmodifiableSetView<String>>;
1515

1616
/// {@template completion_configuration}
@@ -22,11 +22,14 @@ class CompletionConfiguration {
2222
/// {@macro completion_configuration}
2323
const CompletionConfiguration._({
2424
required this.uninstalls,
25+
required this.installs,
2526
});
2627

2728
/// Creates an empty [CompletionConfiguration].
2829
@visibleForTesting
29-
CompletionConfiguration.empty() : uninstalls = UnmodifiableMapView({});
30+
CompletionConfiguration.empty()
31+
: uninstalls = ShellCommandsMap({}),
32+
installs = ShellCommandsMap({});
3033

3134
/// Creates a [CompletionConfiguration] from the given [file] content.
3235
///
@@ -56,17 +59,34 @@ class CompletionConfiguration {
5659
}
5760

5861
return CompletionConfiguration._(
59-
uninstalls: _jsonDecodeUninstalls(decodedJson),
62+
uninstalls: _jsonDecodeShellCommandsMap(
63+
decodedJson,
64+
jsonKey: CompletionConfiguration.uninstallsJsonKey,
65+
),
66+
installs: _jsonDecodeShellCommandsMap(
67+
decodedJson,
68+
jsonKey: CompletionConfiguration.installsJsonKey,
69+
),
6070
);
6171
}
6272

6373
/// The JSON key for the [uninstalls] field.
64-
static const String _uninstallsJsonKey = 'uninstalls';
74+
@visibleForTesting
75+
static const String uninstallsJsonKey = 'uninstalls';
76+
77+
/// The JSON key for the [installs] field.
78+
@visibleForTesting
79+
static const String installsJsonKey = 'installs';
6580

6681
/// Stores those commands that have been manually uninstalled by the user.
6782
///
6883
/// Uninstalls are specific to a given [SystemShell].
69-
final Uninstalls uninstalls;
84+
final ShellCommandsMap uninstalls;
85+
86+
/// Stores those commands that have completion installed.
87+
///
88+
/// Installed commands are specific to a given [SystemShell].
89+
final ShellCommandsMap installs;
7090

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

99+
/// Returns a JSON representation of this [CompletionConfiguration].
100+
String _toJson() {
101+
return jsonEncode({
102+
uninstallsJsonKey: _jsonEncodeShellCommandsMap(uninstalls),
103+
installsJsonKey: _jsonEncodeShellCommandsMap(installs),
104+
});
105+
}
106+
79107
/// Returns a copy of this [CompletionConfiguration] with the given fields
80108
/// replaced.
81109
CompletionConfiguration copyWith({
82-
Uninstalls? uninstalls,
110+
ShellCommandsMap? uninstalls,
111+
ShellCommandsMap? installs,
83112
}) {
84113
return CompletionConfiguration._(
85114
uninstalls: uninstalls ?? this.uninstalls,
115+
installs: installs ?? this.installs,
86116
);
87117
}
88-
89-
/// Returns a JSON representation of this [CompletionConfiguration].
90-
String _toJson() {
91-
return jsonEncode({
92-
_uninstallsJsonKey: _jsonEncodeUninstalls(uninstalls),
93-
});
94-
}
95118
}
96119

97-
/// Decodes [Uninstalls] from the given [json].
120+
/// Decodes [ShellCommandsMap] from the given [json].
98121
///
99122
/// If the [json] is not partially or fully valid, it handles issues gracefully
100123
/// without throwing an [Exception].
101-
Uninstalls _jsonDecodeUninstalls(Map<String, dynamic> json) {
102-
if (!json.containsKey(CompletionConfiguration._uninstallsJsonKey)) {
103-
return UnmodifiableMapView({});
124+
ShellCommandsMap _jsonDecodeShellCommandsMap(
125+
Map<String, dynamic> json, {
126+
required String jsonKey,
127+
}) {
128+
if (!json.containsKey(jsonKey)) {
129+
return ShellCommandsMap({});
104130
}
105-
final jsonUninstalls = json[CompletionConfiguration._uninstallsJsonKey];
106-
if (jsonUninstalls is! String) {
107-
return UnmodifiableMapView({});
131+
final jsonShellCommandsMap = json[jsonKey];
132+
if (jsonShellCommandsMap is! String) {
133+
return ShellCommandsMap({});
108134
}
109-
late final Map<String, dynamic> decodedUninstalls;
135+
late final Map<String, dynamic> decodedShellCommandsMap;
110136
try {
111-
decodedUninstalls = jsonDecode(jsonUninstalls) as Map<String, dynamic>;
137+
decodedShellCommandsMap =
138+
jsonDecode(jsonShellCommandsMap) as Map<String, dynamic>;
112139
} on FormatException {
113-
return UnmodifiableMapView({});
140+
return ShellCommandsMap({});
114141
}
115142

116-
final newUninstalls = <SystemShell, UnmodifiableSetView<String>>{};
117-
for (final entry in decodedUninstalls.entries) {
143+
final newShellCommandsMap = <SystemShell, UnmodifiableSetView<String>>{};
144+
for (final entry in decodedShellCommandsMap.entries) {
118145
final systemShell = SystemShell.tryParse(entry.key);
119146
if (systemShell == null) continue;
120-
final uninstallSet = <String>{};
147+
final commandsSet = <String>{};
121148
if (entry.value is List) {
122149
for (final uninstall in entry.value as List) {
123150
if (uninstall is String) {
124-
uninstallSet.add(uninstall);
151+
commandsSet.add(uninstall);
125152
}
126153
}
127154
}
128-
newUninstalls[systemShell] = UnmodifiableSetView(uninstallSet);
155+
newShellCommandsMap[systemShell] = UnmodifiableSetView(commandsSet);
129156
}
130-
return UnmodifiableMapView(newUninstalls);
157+
return UnmodifiableMapView(newShellCommandsMap);
131158
}
132159

133-
/// Returns a JSON representation of the given [Uninstalls].
134-
String _jsonEncodeUninstalls(Uninstalls uninstalls) {
160+
/// Returns a JSON representation of the given [ShellCommandsMap].
161+
String _jsonEncodeShellCommandsMap(ShellCommandsMap shellCommandsMap) {
135162
return jsonEncode({
136-
for (final entry in uninstalls.entries)
163+
for (final entry in shellCommandsMap.entries)
137164
entry.key.toString(): entry.value.toList(),
138165
});
139166
}
140167

141-
/// Provides convinience methods for [Uninstalls].
142-
extension UninstallsExtension on Uninstalls {
143-
/// Returns a new [Uninstalls] with the given [command] added to
168+
/// Provides convinience methods for [ShellCommandsMap].
169+
extension ShellCommandsMapExtension on ShellCommandsMap {
170+
/// Returns a new [ShellCommandsMap] with the given [command] added to
144171
/// [systemShell].
145-
Uninstalls include({
172+
ShellCommandsMap include({
146173
required String command,
147174
required SystemShell systemShell,
148175
}) {
@@ -159,9 +186,9 @@ extension UninstallsExtension on Uninstalls {
159186
);
160187
}
161188

162-
/// Returns a new [Uninstalls] with the given [command] removed from
189+
/// Returns a new [ShellCommandsMap] with the given [command] removed from
163190
/// [systemShell].
164-
Uninstalls exclude({
191+
ShellCommandsMap exclude({
165192
required String command,
166193
required SystemShell systemShell,
167194
}) {

lib/src/installer/completion_installation.dart

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,8 @@ class CompletionInstallation {
109109
/// the aforementioned config file.
110110
///
111111
/// If [force] is true, it will overwrite the command's completion files even
112-
/// if they already exist. If false, it will check if it has been explicitly
113-
/// uninstalled before installing it.
112+
/// if they already exist. If false, it will check if is already installed, or
113+
/// if it has been explicitly uninstalled before installing it.
114114
void install(String rootCommand, {bool force = false}) {
115115
final configuration = this.configuration;
116116

@@ -147,24 +147,33 @@ class CompletionInstallation {
147147
command: rootCommand,
148148
systemShell: configuration.shell,
149149
),
150+
installs: completionConfiguration.installs.include(
151+
command: rootCommand,
152+
systemShell: configuration.shell,
153+
),
150154
)
151155
.writeTo(completionConfigurationFile);
152156
}
153157

154158
/// Wether the completion configuration files for a [rootCommand] should be
155159
/// installed or not.
156160
///
157-
/// It will return false if the root command has been explicitly uninstalled.
161+
/// It will return false if the root command is already installed or it
162+
/// has been explicitly uninstalled.
158163
bool _shouldInstall(String rootCommand) {
159164
final completionConfiguration = CompletionConfiguration.fromFile(
160165
completionConfigurationFile,
161166
);
162167
final systemShell = configuration!.shell;
168+
final isInstalled = completionConfiguration.installs.contains(
169+
command: rootCommand,
170+
systemShell: systemShell,
171+
);
163172
final isUninstalled = completionConfiguration.uninstalls.contains(
164173
command: rootCommand,
165174
systemShell: systemShell,
166175
);
167-
return !isUninstalled;
176+
return !isInstalled && !isUninstalled;
168177
}
169178

170179
/// Create a directory in which the completion config files shall be saved.
@@ -434,6 +443,10 @@ ${configuration!.sourceLineTemplate(scriptPath)}''';
434443
command: rootCommand,
435444
systemShell: configuration.shell,
436445
),
446+
installs: completionConfiguration.installs.exclude(
447+
command: rootCommand,
448+
systemShell: configuration.shell,
449+
),
437450
)
438451
.writeTo(completionConfigurationFile);
439452
}

0 commit comments

Comments
 (0)