Skip to content

Commit f2cddbc

Browse files
committed
feat(params): Add support for optional parameters in targets
Introduced support for defining optional parameters in build targets. Updated `TargetDefinition` and associated models to differentiate between required and optional parameters. Adjusted related functionality in build execution, parameter validation, and workflow generation. Added tests to validate optional parameter behavior.
1 parent 71d288c commit f2cddbc

File tree

17 files changed

+249
-90
lines changed

17 files changed

+249
-90
lines changed

DecSm.Atom.Module.DevopsWorkflows/Generation/DevopsWorkflowWriter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -534,8 +534,8 @@ private void WriteCommandStep(
534534
continue;
535535

536536
foreach (var input in githubManualTrigger.Inputs.Where(i => target
537-
.RequiredParams
538-
.Select(p => p.ArgName)
537+
.Params
538+
.Select(p => p.Param.ArgName)
539539
.Any(p => p == i.Name)))
540540
env[input.Name] = $"${{{{ parameters.{input.Name} }}}}";
541541
}
@@ -545,12 +545,12 @@ private void WriteCommandStep(
545545
$"$({buildDefinition.ParamDefinitions[consumedVariable.VariableName].ArgName})";
546546

547547
var requiredSecrets = target
548-
.RequiredParams
549-
.Where(x => x.IsSecret)
548+
.Params
549+
.Where(x => x.Param.IsSecret)
550550
.Select(x => x)
551551
.ToArray();
552552

553-
if (requiredSecrets.Any(x => x.IsSecret))
553+
if (requiredSecrets.Any(x => x.Param.IsSecret))
554554
{
555555
foreach (var injectedSecret in workflow.Options.OfType<WorkflowSecretsSecretInjection>())
556556
{
@@ -594,10 +594,10 @@ private void WriteCommandStep(
594594
.Options
595595
.Concat(workflowStep.Options)
596596
.OfType<WorkflowSecretInjection>()
597-
.FirstOrDefault(x => x.Value == requiredSecret.Name);
597+
.FirstOrDefault(x => x.Value == requiredSecret.Param.Name);
598598

599599
if (injectedSecret is not null)
600-
env[requiredSecret.ArgName] = $"$({requiredSecret.ArgName.ToUpper().Replace('-', '_')})";
600+
env[requiredSecret.Param.ArgName] = $"$({requiredSecret.Param.ArgName.ToUpper().Replace('-', '_')})";
601601
}
602602

603603
var environmentInjections = workflow.Options.OfType<WorkflowEnvironmentInjection>();

DecSm.Atom.Module.GithubWorkflows/Generation/GithubWorkflowWriter.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -487,8 +487,8 @@ private void WriteCommandStep(
487487
continue;
488488

489489
foreach (var input in githubManualTrigger.Inputs.Where(i => target
490-
.RequiredParams
491-
.Select(p => p.ArgName)
490+
.Params
491+
.Select(p => p.Param.ArgName)
492492
.Any(p => p == i.Name)))
493493
env[input.Name] = $"${{{{ inputs.{input.Name} }}}}";
494494
}
@@ -498,12 +498,12 @@ private void WriteCommandStep(
498498
$"${{{{ needs.{consumedVariable.TargetName}.outputs.{buildDefinition.ParamDefinitions[consumedVariable.VariableName].ArgName} }}}}";
499499

500500
var requiredSecrets = target
501-
.RequiredParams
502-
.Where(x => x.IsSecret)
501+
.Params
502+
.Where(x => x.Param.IsSecret)
503503
.Select(x => x)
504504
.ToArray();
505505

506-
if (requiredSecrets.Any(x => x.IsSecret))
506+
if (requiredSecrets.Any(x => x.Param.IsSecret))
507507
{
508508
foreach (var injectedSecret in workflow.Options.OfType<WorkflowSecretsSecretInjection>())
509509
{
@@ -547,10 +547,10 @@ private void WriteCommandStep(
547547
.Options
548548
.Concat(workflowStep.Options)
549549
.OfType<WorkflowSecretInjection>()
550-
.FirstOrDefault(x => x.Value == requiredSecret.Name);
550+
.FirstOrDefault(x => x.Value == requiredSecret.Param.Name);
551551

552552
if (injectedSecret is not null)
553-
env[requiredSecret.ArgName] = $"${{{{ secrets.{requiredSecret.ArgName.ToUpper().Replace('-', '_')} }}}}";
553+
env[requiredSecret.Param.ArgName] = $"${{{{ secrets.{requiredSecret.Param.ArgName.ToUpper().Replace('-', '_')} }}}}";
554554
}
555555

556556
var environmentInjections = workflow.Options.OfType<WorkflowEnvironmentInjection>();

DecSm.Atom.SourceGenerators/GenerateInterfaceMembersSourceGenerator.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@ private static void GenerateCode(
5656
GeneratePartial(context, classSymbol, classDeclarationSyntax);
5757
}
5858

59-
private static string SimpleName(string fullName) =>
60-
fullName
61-
.Split('.')
62-
.Last();
63-
6459
private static void GeneratePartial(
6560
SourceProductionContext context,
6661
INamedTypeSymbol classSymbol,
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
namespace DecSm.Atom.Tests.BuildTests.Params;
2+
3+
[BuildDefinition]
4+
public partial class OptionalParamBuild : BuildDefinition, IOptionalParamTarget1
5+
{
6+
public string? ExecuteValue1 { get; set; }
7+
8+
public string? ExecuteValue2 { get; set; }
9+
}
10+
11+
[TargetDefinition]
12+
public partial interface IOptionalParamTarget1
13+
{
14+
[ParamDefinition("param-1", "Param 1")]
15+
string? Param1 => GetParam(() => Param1);
16+
17+
[ParamDefinition("param-2", "Param 2")]
18+
string? Param2 => GetParam(() => Param2);
19+
20+
string? ExecuteValue1 { get; set; }
21+
22+
string? ExecuteValue2 { get; set; }
23+
24+
Target OptionalParamTarget1 =>
25+
t => t
26+
.RequiresParam(nameof(Param1))
27+
.UsesParam(nameof(Param2))
28+
.Executes(() =>
29+
{
30+
ExecuteValue1 = Param1;
31+
ExecuteValue2 = Param2;
32+
33+
return Task.CompletedTask;
34+
});
35+
}

DecSm.Atom.Tests/BuildTests/Params/ParamTests.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,4 +55,29 @@ public void Param_WhenRequiredAndNotSupplied_StopsAndReturnsError()
5555
.ToString()
5656
.ShouldContain("Missing required parameter 'param-2' for target ParamTarget2");
5757
}
58+
59+
[Test]
60+
public void Param_WhenOptionalAndNotSupplied_UsesDefaultValue()
61+
{
62+
// Arrange
63+
var loggerProvider = new TestLoggerProvider();
64+
65+
var host = CreateTestHost<OptionalParamBuild>(commandLineArgs: new(true,
66+
[
67+
new CommandArg(nameof(IOptionalParamTarget1.OptionalParamTarget1)),
68+
new ParamArg("param-1", nameof(IOptionalParamTarget1.Param1), "TestValue"),
69+
]),
70+
configure: builder => builder.Logging.AddProvider(loggerProvider));
71+
72+
var build = (OptionalParamBuild)host.Services.GetRequiredService<IBuildDefinition>();
73+
74+
// Act
75+
host.Run();
76+
77+
// Assert
78+
TestContext.Out.WriteLine(loggerProvider.Logger.LogContent.ToString());
79+
80+
build.ExecuteValue1.ShouldBe("TestValue");
81+
build.ExecuteValue2.ShouldBeNull();
82+
}
5883
}

DecSm.Atom.Tests/ClassTests/Build/BuildExecutorTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ public async Task Execute_WhenBuildIsValid_SucceedsAndLogs()
8585
return Task.CompletedTask;
8686
},
8787
],
88-
RequiredParams = [],
88+
Params = [],
8989
ConsumedArtifacts = [],
9090
ProducedArtifacts = [],
9191
ConsumedVariables = [],

DecSm.Atom.Tests/ClassTests/Build/Definition/TargetDefinitionTests.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,9 +111,10 @@ public void RequiresParam_AddsRequiredParam()
111111
targetDefinition.RequiresParam(paramName);
112112

113113
// Assert
114-
targetDefinition.RequiredParams.ShouldSatisfyAllConditions(x => x.ShouldNotBeEmpty(),
114+
targetDefinition.Params.ShouldSatisfyAllConditions(x => x.ShouldNotBeEmpty(),
115115
x => x.Count.ShouldBe(1),
116116
x => x[0]
117+
.Param
117118
.ShouldBe(paramName));
118119
}
119120

DecSm.Atom.Tests/ClassTests/Build/Model/BuildModelTests.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ public void CurrentTarget_WhenNoTargets_ReturnsNull()
2525
new("TargetModel", null, false)
2626
{
2727
Tasks = [],
28-
RequiredParams = [],
28+
Params = [],
2929
ConsumedArtifacts = [],
3030
ProducedArtifacts = [],
3131
ConsumedVariables = [],

DecSm.Atom.Tool/Commands/RunHandler.cs

Lines changed: 106 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -6,57 +6,131 @@ public static async Task<int> Handle(ParseResult parseResult, CancellationToken
66
{
77
var runArgs = parseResult.GetRequiredValue(Model.RunArgs);
88
var projectOption = parseResult.GetValue(Model.ProjectOption);
9+
var fileOption = parseResult.GetValue(Model.FileOption);
10+
11+
if (fileOption is { Length: > 0 })
12+
return await RunFile(fileOption, runArgs, cancellationToken);
913

1014
var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
1115

1216
while (currentDirectory?.Exists is true)
1317
{
1418
if (Directory.Exists(Path.Combine(currentDirectory.FullName, "_atom")))
19+
return await RunProject(currentDirectory, projectOption, runArgs, cancellationToken);
20+
21+
currentDirectory = currentDirectory.Parent;
22+
}
23+
24+
Console.WriteLine("No Atom project found.");
25+
26+
return 1;
27+
}
28+
29+
private static async Task<int> RunProject(
30+
DirectoryInfo currentDirectory,
31+
string? projectOption,
32+
string[] runArgs,
33+
CancellationToken cancellationToken)
34+
{
35+
// Sanitize arguments
36+
var escapedArgs = runArgs.Select(arg =>
37+
{
38+
arg = arg
39+
.Replace("\n", string.Empty)
40+
.Replace("\r", string.Empty);
41+
42+
return arg.Contains(';') || arg.Contains('&') || arg.Contains('|') || arg.Contains(' ')
43+
? $"\"{arg}\""
44+
: arg;
45+
});
46+
47+
var atomProjectName = "_atom";
48+
49+
for (var i = 0; i < runArgs.Length; i++)
50+
if (projectOption is { Length: > 0 })
1551
{
16-
// Sanitize arguments
17-
var escapedArgs = runArgs.Select(arg =>
52+
atomProjectName = projectOption
53+
.Replace("\n", string.Empty)
54+
.Replace("\r", string.Empty);
55+
56+
if (atomProjectName.Contains(';') ||
57+
atomProjectName.Contains('&') ||
58+
atomProjectName.Contains('|') ||
59+
atomProjectName.Contains(' '))
60+
atomProjectName = $"\"{atomProjectName}\"";
61+
62+
break;
63+
}
64+
65+
var atomProjectPath = Path.Combine(currentDirectory.FullName, atomProjectName, $"{atomProjectName}.csproj");
66+
var allArgs = new[] { "run", "--project", atomProjectPath, "--" }.Concat(escapedArgs);
67+
68+
var atomProcess = Process.Start("dotnet", allArgs);
69+
await atomProcess.WaitForExitAsync(cancellationToken);
70+
71+
return atomProcess.ExitCode;
72+
}
73+
74+
private static async Task<int> RunFile(string? fileOption, string[] runArgs, CancellationToken cancellationToken)
75+
{
76+
// Sanitize arguments
77+
var escapedArgs = runArgs.Select(arg =>
78+
{
79+
arg = arg
80+
.Replace("\n", string.Empty)
81+
.Replace("\r", string.Empty);
82+
83+
return arg.Contains(';') || arg.Contains('&') || arg.Contains('|') || arg.Contains(' ')
84+
? $"\"{arg}\""
85+
: arg;
86+
});
87+
88+
string[] possibleAtomFileNames = ["Atom.cs", "_atom.cs", "Program.cs", "App.cs"];
89+
string? atomFilePath = null;
90+
var currentDirectory = new DirectoryInfo(Directory.GetCurrentDirectory());
91+
92+
foreach (var possibleAtomFileName in possibleAtomFileNames)
93+
{
94+
var atomFileName = possibleAtomFileName;
95+
96+
for (var i = 0; i < runArgs.Length; i++)
97+
if (fileOption is { Length: > 0 })
1898
{
19-
arg = arg
99+
atomFileName = fileOption
20100
.Replace("\n", string.Empty)
21101
.Replace("\r", string.Empty);
22102

23-
return arg.Contains(';') || arg.Contains('&') || arg.Contains('|') || arg.Contains(' ')
24-
? $"\"{arg}\""
25-
: arg;
26-
});
27-
28-
var atomProjectName = "_atom";
29-
30-
for (var i = 0; i < runArgs.Length; i++)
31-
if (projectOption is { Length: > 0 })
32-
{
33-
atomProjectName = projectOption
34-
.Replace("\n", string.Empty)
35-
.Replace("\r", string.Empty);
103+
if (atomFileName.Contains(';') ||
104+
atomFileName.Contains('&') ||
105+
atomFileName.Contains('|') ||
106+
atomFileName.Contains(' '))
107+
atomFileName = $"\"{atomFileName}\"";
36108

37-
if (atomProjectName.Contains(';') ||
38-
atomProjectName.Contains('&') ||
39-
atomProjectName.Contains('|') ||
40-
atomProjectName.Contains(' '))
41-
atomProjectName = $"\"{atomProjectName}\"";
109+
break;
110+
}
42111

43-
break;
44-
}
112+
atomFilePath = Path.IsPathRooted(atomFileName)
113+
? atomFileName
114+
: Path.Combine(currentDirectory.FullName, atomFileName);
45115

46-
var atomProjectPath = Path.Combine(currentDirectory.FullName, atomProjectName, $"{atomProjectName}.csproj");
47-
var allArgs = new[] { "run", "--project", atomProjectPath, "--" }.Concat(escapedArgs);
116+
if (File.Exists(atomFilePath))
117+
break;
48118

49-
var atomProcess = Process.Start("dotnet", allArgs);
50-
await atomProcess.WaitForExitAsync(cancellationToken);
119+
atomFilePath = null;
120+
}
51121

52-
return atomProcess.ExitCode;
53-
}
122+
if (atomFilePath is null)
123+
{
124+
Console.WriteLine("No Atom file found.");
54125

55-
currentDirectory = currentDirectory.Parent;
126+
return 1;
56127
}
57128

58-
Console.WriteLine("No Atom project found.");
129+
var allArgs = new[] { "run", atomFilePath, "--" }.Concat(escapedArgs);
59130

60-
return 1;
131+
var atomProcess = Process.Start("dotnet", allArgs);
132+
await atomProcess.WaitForExitAsync(cancellationToken);
133+
134+
return atomProcess.ExitCode;
61135
}
62136
}

DecSm.Atom.Tool/Model.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,21 @@
22

33
internal static class Model
44
{
5-
public static readonly Argument<string[]> RunArgs = new("runArgs");
5+
public static readonly Argument<string[]> RunArgs = new("runArgs")
6+
{
7+
DefaultValueFactory = _ => [],
8+
};
69

710
public static readonly Option<string> ProjectOption = new("--project", "-p")
811
{
912
Description = "The project to run.",
1013
};
1114

15+
public static readonly Option<string> FileOption = new("--file", "-f")
16+
{
17+
Description = "The file to run.",
18+
};
19+
1220
public static readonly Option<string> NameOption = new("--name", "-n")
1321
{
1422
Description = "The name of the feed to add/remove.",
@@ -23,6 +31,7 @@ internal static class Model
2331
{
2432
RunArgs,
2533
ProjectOption,
34+
FileOption,
2635
}.WithAction(RunHandler.Handle);
2736

2837
private static readonly Command NugetAddCommand = new Command("nuget-add")
@@ -35,6 +44,7 @@ internal static class Model
3544
{
3645
RunArgs,
3746
ProjectOption,
47+
FileOption,
3848
RunCommand,
3949
NugetAddCommand,
4050
}.WithAction(RunHandler.Handle);

0 commit comments

Comments
 (0)