Skip to content

Commit 0e54a66

Browse files
authored
Merge pull request #79 from xt0rted/globbing
Support for globbing in script names
2 parents 407397a + a1da751 commit 0e54a66

17 files changed

+338
-25
lines changed

CHANGELOG.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
### Added
99

1010
- Ability to run multiple scripts in a single call (e.g. `dotnet r build test pack`) ([#10](https://github.com/xt0rted/dotnet-run-script/pull/10))
11+
- Support for globbing in script names (e.g `dotnet r test:*` will match `test:unit` and `test:integration`) ([#79](https://github.com/xt0rted/dotnet-run-script/pull/79))
1112

1213
### Updated
1314

@@ -22,7 +23,8 @@
2223

2324
## [0.2.0](https://github.com/xt0rted/dotnet-run-script/compare/v0.1.0...v0.2.0) - 2022-04-23
2425

25-
> ℹ️ This version broke conditional script execution (`cmd1 && cmd2`) in `cmd.exe`
26+
> **Note**
27+
> This version broke conditional script execution (`cmd1 && cmd2`) in `cmd.exe`
2628
2729
### Added
2830

README.md

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ dotnet new tool-manifest
2121
dotnet tool install run-script
2222
```
2323

24-
> ⚠️ It's not recommended to install this tool globally.
24+
> **Warning**
25+
> Installing this tool globally is not recommended.
2526
> PowerShell defines the alias `r` for the `Invoke-History` command which prevents this from being called.
2627
> You'll also run into issues calling this from your scripts since global tools don't use the `dotnet` prefix.
2728
@@ -79,9 +80,10 @@ In your project's `global.json` add a `scripts` object:
7980
}
8081
```
8182

82-
ℹ️ The shell used depends on the OS.
83-
On Windows `CMD` is used, on Linux, macOS, and WSL `sh` is used.
84-
This can be overridden by setting the `scriptShell` property or by passing the `--script-shell` option with the name of the shell to use.
83+
> **Note**
84+
> The shell used depends on the OS.
85+
> On Windows `CMD` is used, on Linux, macOS, and WSL `sh` is used.
86+
> This can be overridden by setting the `scriptShell` property or by passing the `--script-shell` option with the name of the shell to use.
8587
8688
The `env` command is a special built-in command that lists all available environment variables.
8789
You can override this with your own command if you wish.
@@ -139,6 +141,13 @@ In this example both the `--configuration` and `--framework` options will be pas
139141
dotnet r build test:unit test:integration package -- --configuration Release --framework net6.0
140142
```
141143

144+
### Globbing or wildcard support
145+
146+
Multiple scripts can be run at the same time using globbing.
147+
This means `dotnet r test:*` will match `test:unit` and `test:integration` and run them in series in the order they're listed in the `global.json` file.
148+
149+
Globbing is handled by the [DotNet.Glob](https://github.com/dazinator/DotNet.Glob) library and currently supports all of its patterns and wildcards.
150+
142151
### Working directory
143152

144153
The working directory is set to the root of the project where the `global.json` is located.

package-lock.json

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/Polyfills/IsExternalInit.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#if !NET5_0_OR_GREATER
2+
namespace System.Runtime.CompilerServices
3+
{
4+
using System.ComponentModel;
5+
6+
/// <summary>
7+
/// Reserved to be used by the compiler for tracking metadata.
8+
/// This class should not be used by developers in source code.
9+
/// </summary>
10+
[EditorBrowsable(EditorBrowsableState.Never)]
11+
internal static class IsExternalInit
12+
{
13+
}
14+
}
15+
#endif

src/Polyfills.cs renamed to src/Polyfills/TaskExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ namespace RunScript;
55

66
using System.Diagnostics;
77

8-
internal static class Polyfills
8+
internal static class TaskExtensions
99
{
1010
#if !NET5_0_OR_GREATER
1111
public static async Task WaitForExitAsync(this Process process, CancellationToken cancellationToken)

src/RunResult.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace RunScript;
2+
3+
internal record RunResult(string Name, int ExitCode);

src/RunScriptCommand.cs

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ namespace RunScript;
22

33
using System.CommandLine.Invocation;
44

5+
using DotNet.Globbing;
6+
57
internal class RunScriptCommand : RootCommand, ICommandHandler
68
{
79
private readonly IEnvironment _environment;
@@ -77,32 +79,29 @@ public async Task<int> InvokeAsync(InvocationContext context)
7779
return 0;
7880
}
7981

80-
// The `env` script is special so if it's not explicitly declared we act like it was
81-
var scriptsToRun = scripts
82-
.Select(script => (name: script, exists: project.Scripts!.ContainsKey(script) || script == "env"))
83-
.ToList();
82+
var scriptsToRun = FindScripts(project.Scripts!, scripts);
8483

8584
// When `--if-present` isn't specified and a script wasn't found in the config then we show an error and stop
86-
if (scriptsToRun.Any(s => !s.exists) && !ifPresent)
85+
if (scriptsToRun.Any(s => !s.Exists) && !ifPresent)
8786
{
8887
writer.Error(
8988
"Script not found: {0}",
9089
string.Join(
9190
", ",
9291
scriptsToRun
93-
.Where(script => !script.exists)
94-
.Select(script => script.name)));
92+
.Where(script => !script.Exists)
93+
.Select(script => script.Name)));
9594

9695
return 1;
9796
}
9897

99-
var runResults = new List<(string scriptName, int exitCode)>();
98+
var runResults = new List<RunResult>();
10099

101-
foreach (var (scriptName, scriptExists) in scriptsToRun)
100+
foreach (var script in scriptsToRun)
102101
{
103-
if (!scriptExists)
102+
if (!script.Exists)
104103
{
105-
writer.Banner($"Skipping script {scriptName}");
104+
writer.Banner($"Skipping script {script.Name}");
106105

107106
continue;
108107
}
@@ -115,10 +114,10 @@ public async Task<int> InvokeAsync(InvocationContext context)
115114
var scriptRunner = builder.CreateGroupRunner(context.GetCancellationToken());
116115

117116
var result = await scriptRunner.RunAsync(
118-
scriptName,
117+
script.Name,
119118
scriptArgs);
120119

121-
runResults.Add((scriptName, result));
120+
runResults.Add(new(script.Name, result));
122121

123122
if (result != 0)
124123
{
@@ -129,24 +128,70 @@ public async Task<int> InvokeAsync(InvocationContext context)
129128
return RunResults(writer, runResults);
130129
}
131130

132-
private static int RunResults(IConsoleWriter writer, List<(string scriptName, int exitCode)> results)
131+
internal static List<ScriptResult> FindScripts(
132+
IDictionary<string, string?> projectScripts,
133+
string[] scripts)
134+
{
135+
var results = new List<ScriptResult>();
136+
137+
foreach (var script in scripts)
138+
{
139+
// The `env` script is special so if it's not explicitly declared we act like it was
140+
if (projectScripts.ContainsKey(script) || string.Equals(script, "env", StringComparison.OrdinalIgnoreCase))
141+
{
142+
results.Add(new(script, true));
143+
144+
continue;
145+
}
146+
147+
var hadMatch = false;
148+
var matcher = Glob.Parse(
149+
script,
150+
new GlobOptions
151+
{
152+
Evaluation =
153+
{
154+
CaseInsensitive = true,
155+
}
156+
});
157+
158+
foreach (var projectScript in projectScripts.Keys)
159+
{
160+
if (matcher.IsMatch(projectScript.AsSpan()))
161+
{
162+
hadMatch = true;
163+
164+
results.Add(new(projectScript, true));
165+
}
166+
}
167+
168+
if (!hadMatch)
169+
{
170+
results.Add(new(script, false));
171+
}
172+
}
173+
174+
return results;
175+
}
176+
177+
internal static int RunResults(IConsoleWriter writer, List<RunResult> results)
133178
{
134179
// If only 1 script ran we don't need a report of the results
135180
if (results.Count == 1)
136181
{
137-
return results[0].exitCode;
182+
return results[0].ExitCode;
138183
}
139184

140185
var hadError = false;
141186

142-
foreach (var (scriptName, exitCode) in results.Where(r => r.exitCode != 0))
187+
foreach (var result in results.Where(r => r.ExitCode != 0))
143188
{
144189
hadError = true;
145190

146191
writer.Line(
147192
"ERROR: \"{0}\" exited with {1}",
148-
writer.ColorText(ConsoleColor.Blue, scriptName),
149-
writer.ColorText(ConsoleColor.Green, exitCode));
193+
writer.ColorText(ConsoleColor.Blue, result.Name),
194+
writer.ColorText(ConsoleColor.Green, result.ExitCode));
150195
}
151196

152197
return hadError ? 1 : 0;

src/ScriptResult.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
namespace RunScript;
2+
3+
internal record ScriptResult(string Name, bool Exists);

src/run-script.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
</ItemGroup>
4747

4848
<ItemGroup>
49+
<PackageReference Include="DotNet.Glob" Version="3.1.3" />
4950
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1">
5051
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
5152
<PrivateAssets>all</PrivateAssets>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
IsErrorRedirected: false,
3+
IsInputRedirected: false,
4+
IsOutputRedirected: false,
5+
Out:
6+
ERROR: "test" exited with 1
7+
8+
}

0 commit comments

Comments
 (0)