Skip to content

Commit f5ce0bb

Browse files
committed
Use KeyedCollection instead of Dictionary
1 parent 9f2267c commit f5ce0bb

20 files changed

+123
-64
lines changed

CHANGELOG.md

+3
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22

33
## Unreleased
44

5+
- Script names are no longer case insensitive which matches NPM's behavior
6+
- Project scripts are no longer backed by `Dictionary<string, string>` which should guarantee they're executed in the order they're found in the `global.json`
7+
58
## [0.5.0](https://github.com/xt0rted/dotnet-run-script/compare/v0.4.0...v0.5.0) - 2022-10-11
69

710
### Added

Directory.Build.props

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
<Project>
22

33
<PropertyGroup>
4-
<TargetFrameworks>netcoreapp3.1;net6.0</TargetFrameworks>
4+
<TargetFrameworks>net6.0;netcoreapp3.1</TargetFrameworks>
55
<LangVersion>latest</LangVersion>
66
<ImplicitUsings>enable</ImplicitUsings>
77
<Nullable>enable</Nullable>

src/CommandGroupRunner.cs

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,15 @@ internal class CommandGroupRunner : ICommandGroupRunner
66
{
77
private readonly IConsoleWriter _writer;
88
private readonly IEnvironment _environment;
9-
private readonly IDictionary<string, string?> _scripts;
9+
private readonly ScriptCollection _scripts;
1010
private readonly ProcessContext _processContext;
1111
private readonly bool _captureOutput;
1212
private readonly CancellationToken _cancellationToken;
1313

1414
public CommandGroupRunner(
1515
IConsoleWriter writer,
1616
IEnvironment environment,
17-
IDictionary<string, string?> scripts,
17+
ScriptCollection scripts,
1818
ProcessContext processContext,
1919
bool captureOutput,
2020
CancellationToken cancellationToken)
@@ -38,10 +38,10 @@ public async Task<int> RunAsync(string name, string[]? scriptArgs)
3838
{
3939
var scriptNames = ImmutableArray.Create(new[] { "pre" + name, name, "post" + name });
4040

41-
foreach (var subScript in scriptNames.Where(scriptName => _scripts.ContainsKey(scriptName) || scriptName == "env"))
41+
foreach (var subScript in scriptNames.Where(scriptName => _scripts.Contains(scriptName) || scriptName == "env"))
4242
{
4343
// At this point we should have done enough checks to make sure the only not found script is `env`
44-
if (!_scripts.ContainsKey(subScript))
44+
if (!_scripts.Contains(subScript))
4545
{
4646
GlobalCommands.PrintEnvironmentVariables(_writer, _environment);
4747

@@ -56,7 +56,7 @@ public async Task<int> RunAsync(string name, string[]? scriptArgs)
5656

5757
var result = await command.RunAsync(
5858
subScript,
59-
_scripts[subScript]!,
59+
_scripts[subScript].Script,
6060
args);
6161

6262
if (result != 0)

src/GlobalCommands.cs

+4-4
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ internal static class GlobalCommands
77
/// </summary>
88
/// <param name="writer">The console logger instance to use.</param>
99
/// <param name="scripts">The project's scripts.</param>
10-
public static void PrintAvailableScripts(IConsoleWriter writer, IDictionary<string, string?> scripts)
10+
public static void PrintAvailableScripts(IConsoleWriter writer, ScriptCollection scripts)
1111
{
1212
writer.Line("Available via `{0}`:", writer.ColorText(ConsoleColor.Blue, "dotnet r"));
1313
writer.BlankLine();
1414

15-
foreach (var script in scripts.Keys)
15+
foreach (var (name, script) in scripts)
1616
{
17-
writer.Line(" {0}", script);
18-
writer.SecondaryLine(" {0}", scripts[script]);
17+
writer.Line(" {0}", name);
18+
writer.SecondaryLine(" {0}", script);
1919
writer.BlankLine();
2020
}
2121
}

src/Project.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@ namespace RunScript;
22

33
using System.Text.Json.Serialization;
44

5-
using RunScript.Serialization;
6-
75
public class Project
86
{
7+
[JsonPropertyName("scriptShell")]
98
public string? ScriptShell { get; set; }
109

11-
[JsonConverter(typeof(CaseInsensitiveDictionaryConverter<string?>))]
12-
public Dictionary<string, string?>? Scripts { get; set; }
10+
[JsonPropertyName("scripts")]
11+
public ScriptCollection? Scripts { get; set; }
1312
}

src/ProjectLoader.cs

+3-4
Original file line numberDiff line numberDiff line change
@@ -57,10 +57,9 @@ internal class ProjectLoader
5757
return JsonSerializer.Deserialize<Project>(
5858
json,
5959
new JsonSerializerOptions
60-
{
61-
PropertyNameCaseInsensitive = true,
62-
ReadCommentHandling = JsonCommentHandling.Skip,
63-
});
60+
{
61+
ReadCommentHandling = JsonCommentHandling.Skip,
62+
});
6463
}
6564
catch
6665
{

src/RunScriptCommand.cs

+3-3
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,15 @@ public async Task<int> InvokeAsync(InvocationContext context)
134134
}
135135

136136
internal static List<ScriptResult> FindScripts(
137-
IDictionary<string, string?> projectScripts,
137+
ScriptCollection projectScripts,
138138
string[] scripts)
139139
{
140140
var results = new List<ScriptResult>();
141141

142142
foreach (var script in scripts)
143143
{
144144
// The `env` script is special so if it's not explicitly declared we act like it was
145-
if (projectScripts.ContainsKey(script) || string.Equals(script, "env", StringComparison.OrdinalIgnoreCase))
145+
if (projectScripts.Contains(script) || string.Equals(script, "env", StringComparison.Ordinal))
146146
{
147147
results.Add(new(script, true));
148148

@@ -160,7 +160,7 @@ internal static List<ScriptResult> FindScripts(
160160
}
161161
});
162162

163-
foreach (var projectScript in projectScripts.Keys)
163+
foreach (var (projectScript, _) in projectScripts)
164164
{
165165
if (matcher.IsMatch(projectScript.AsSpan()))
166166
{

src/ScriptCollection.cs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace RunScript;
2+
3+
using System.Collections.ObjectModel;
4+
using System.Text.Json.Serialization;
5+
6+
using RunScript.Serialization;
7+
8+
[JsonConverter(typeof(ScriptCollectionConverter))]
9+
public class ScriptCollection : KeyedCollection<string, ScriptMapping>
10+
{
11+
protected override string GetKeyForItem(ScriptMapping item)
12+
{
13+
if (item is null) throw new ArgumentNullException(nameof(item));
14+
15+
return item.Name;
16+
}
17+
18+
public void Add(string name, string script)
19+
=> Add(new ScriptMapping(name, script));
20+
}

src/ScriptMapping.cs

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace RunScript;
2+
3+
public record ScriptMapping(string Name, string Script);

src/Serialization/CaseInsensitiveDictionaryConverter.cs

-33
This file was deleted.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
namespace RunScript.Serialization;
2+
3+
using System.Text.Json;
4+
using System.Text.Json.Serialization;
5+
6+
internal class ScriptCollectionConverter : JsonConverter<ScriptCollection>
7+
{
8+
public override ScriptCollection Read(
9+
ref Utf8JsonReader reader,
10+
Type typeToConvert,
11+
JsonSerializerOptions options)
12+
{
13+
var scripts = new ScriptCollection();
14+
15+
using (var jsonDoc = JsonDocument.ParseValue(ref reader))
16+
using (var jsonScripts = jsonDoc.RootElement.EnumerateObject())
17+
{
18+
foreach (var script in jsonScripts)
19+
{
20+
scripts.Add(script.Name, script.Value.ToString());
21+
}
22+
}
23+
24+
return scripts;
25+
}
26+
27+
public override void Write(
28+
Utf8JsonWriter writer,
29+
ScriptCollection value,
30+
JsonSerializerOptions options)
31+
{
32+
writer.WriteStartObject();
33+
34+
foreach (var element in value)
35+
{
36+
writer.WritePropertyName(element.Name);
37+
writer.WriteStringValue(element.Script);
38+
}
39+
40+
writer.WriteEndObject();
41+
}
42+
}

test/CommandBuilderTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ private static CommandBuilder SetUpTest(bool isWindows, string? comSpec = Defaul
101101

102102
var project = new Project
103103
{
104-
Scripts = new Dictionary<string, string?>(),
104+
Scripts = new ScriptCollection(),
105105
};
106106

107107
var environment = new TestEnvironment(

test/CommandGroupRunnerTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -240,7 +240,7 @@ public async Task Should_return_first_error_hit(bool isWindows)
240240
private static (TestConsole console, CommandGroupRunner groupRunner) SetUpTest(
241241
TestCommandRunner[] commandRunners,
242242
bool isWindows,
243-
Action<Dictionary<string, string?>>? scriptSetup = null)
243+
Action<ScriptCollection>? scriptSetup = null)
244244
{
245245
var console = new TestConsole();
246246
var consoleFormatProvider = new ConsoleFormatInfo
@@ -249,7 +249,7 @@ private static (TestConsole console, CommandGroupRunner groupRunner) SetUpTest(
249249
};
250250
var consoleWriter = new ConsoleWriter(console, consoleFormatProvider, verbose: true);
251251

252-
var scripts = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
252+
var scripts = new ScriptCollection
253253
{
254254
// clean
255255
{ "clean", "echo clean" },

test/GlobalCommandsTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ public async Task Should_log_all_available_scripts()
2020
};
2121
var consoleWriter = new ConsoleWriter(console, consoleFormatProvider, verbose: true);
2222

23-
var scripts = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
23+
var scripts = new ScriptCollection
2424
{
2525
{ "clean", "echo clean" },
2626
{ "prebuild", "echo prebuild" },

test/Integration/CommandBuilderTests.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ private static async Task Should_execute_single_script_in_shell(bool isWindows,
5353

5454
var project = new Project
5555
{
56-
Scripts = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
56+
Scripts = new ScriptCollection
5757
{
5858
{ "test", "echo testing" },
5959
},

test/ModuleInitializer.cs

+1
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,6 @@ public static class ModuleInitializer
88
public static void Init()
99
{
1010
VerifierSettings.AddExtraSettings(settings => settings.Converters.Add(new ConsoleConverter()));
11+
VerifierSettings.AddExtraSettings(settings => settings.Converters.Add(new ScriptCollectionConverter()));
1112
}
1213
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
Scripts: {
3+
bUiLD: build,
4+
TEST: test,
5+
pack: pack
6+
}
7+
}

test/ProjectLoaderTests.cs

+1-3
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public async Task Should_look_up_the_tree()
8585
}
8686

8787
[Fact]
88-
public async Task Should_treat_script_names_as_lowercase()
88+
public async Task Should_not_treat_script_names_as_always_lowercase()
8989
{
9090
// Given
9191
var testPath = TestPath("script-names");
@@ -95,8 +95,6 @@ public async Task Should_treat_script_names_as_lowercase()
9595
var (project, _) = await projectLoader.LoadAsync(testPath);
9696

9797
// Then
98-
project.Scripts?.Comparer.ShouldBe(StringComparer.OrdinalIgnoreCase);
99-
10098
await Verify(project);
10199
}
102100

test/RunScriptCommandTests.cs

+2-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,11 @@ public static class RunScriptCommandTests
99
[Trait("category", "unit")]
1010
public class FindScripts
1111
{
12-
private readonly Dictionary<string, string?> _projectScripts;
12+
private readonly ScriptCollection _projectScripts;
1313

1414
public FindScripts()
1515
{
16-
_projectScripts = new Dictionary<string, string?>(StringComparer.OrdinalIgnoreCase)
16+
_projectScripts = new ScriptCollection
1717
{
1818
{ "clean", "echo clean" },
1919
{ "prebuild", "echo prebuild" },

test/ScriptCollectionConverter.cs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace RunScript;
2+
3+
public class ScriptCollectionConverter : WriteOnlyJsonConverter<ScriptCollection>
4+
{
5+
public override void Write(VerifyJsonWriter writer, ScriptCollection value)
6+
{
7+
if (writer is null) throw new ArgumentNullException(nameof(writer));
8+
if (value is null) throw new ArgumentNullException(nameof(value));
9+
10+
writer.WriteStartObject();
11+
12+
foreach (var (name, script) in value)
13+
{
14+
writer.WritePropertyName(name);
15+
writer.WriteValue(script);
16+
}
17+
18+
writer.WriteEndObject();
19+
}
20+
}

0 commit comments

Comments
 (0)