Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
136 changes: 80 additions & 56 deletions src/ConsoleAppFramework/Emitter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -136,88 +136,112 @@ public void EmitRun(SourceBuilder sb, CommandWithId commandWithId, bool isRunAsy

using (command.HasFilter ? sb.Nop : sb.BeginBlock("try"))
{
if (hasArgument)
{
sb.AppendLine("var argumentPosition = 0;");
}

using (sb.BeginBlock("for (int i = 0; i < commandArgs.Length; i++)"))
{
// parse indexed argument([Argument] parameter)
if (hasArgument)
sb.AppendLine("var name = commandArgs[i];");
sb.AppendLine("var optionMatched = false;");
sb.AppendLine("var optionCandidate = name.Length > 1 && name[0] == '-' && !char.IsDigit(name[1]);");
sb.AppendLine();

if (!command.Parameters.All(p => !p.IsParsable || p.IsArgument))
{
for (int i = 0; i < command.Parameters.Length; i++)
using (sb.BeginBlock("if (optionCandidate)"))
{
var parameter = command.Parameters[i];
if (!parameter.IsArgument) continue;

sb.AppendLine($"if (i == {parameter.ArgumentIndex})");
using (sb.BeginBlock())
using (sb.BeginBlock("switch (name)"))
{
sb.AppendLine($"{parameter.BuildParseMethod(i, parameter.Name, increment: false)}");
if (parameter.RequireCheckArgumentParsed)
// parse argument(fast, switch directly)
for (int i = 0; i < command.Parameters.Length; i++)
{
sb.AppendLine($"arg{i}Parsed = true;");
var parameter = command.Parameters[i];
if (!parameter.IsParsable) continue;
if (parameter.IsArgument) continue;

sb.AppendLine($"case \"--{parameter.Name}\":");
foreach (var alias in parameter.Aliases)
{
sb.AppendLine($"case \"{alias}\":");
}
using (sb.BeginBlock())
{
sb.AppendLine($"{parameter.BuildParseMethod(i, parameter.Name, increment: true)}");
if (parameter.RequireCheckArgumentParsed)
{
sb.AppendLine($"arg{i}Parsed = true;");
}
sb.AppendLine("optionMatched = true;");
sb.AppendLine("break;");
}
}

using (sb.BeginIndent("default:"))
{
// parse argument(slow, ignorecase)
for (int i = 0; i < command.Parameters.Length; i++)
{
var parameter = command.Parameters[i];
if (!parameter.IsParsable) continue;
if (parameter.IsArgument) continue;

sb.AppendLine($"if (string.Equals(name, \"--{parameter.Name}\", StringComparison.OrdinalIgnoreCase){(parameter.Aliases.Length == 0 ? ")" : "")}");
for (int j = 0; j < parameter.Aliases.Length; j++)
{
var alias = parameter.Aliases[j];
sb.AppendLine($" || string.Equals(name, \"{alias}\", StringComparison.OrdinalIgnoreCase){(parameter.Aliases.Length == j + 1 ? ")" : "")}");
}
using (sb.BeginBlock())
{
sb.AppendLine($"{parameter.BuildParseMethod(i, parameter.Name, increment: true)}");
if (parameter.RequireCheckArgumentParsed)
{
sb.AppendLine($"arg{i}Parsed = true;");
}
sb.AppendLine("optionMatched = true;");
sb.AppendLine($"break;");
}
}

sb.AppendLine("ThrowArgumentNameNotFound(name);");
sb.AppendLine("break;");
}
}

sb.AppendLine("if (optionMatched)");
using (sb.BeginBlock())
{
sb.AppendLine("continue;");
}
}
sb.AppendLine();
}

sb.AppendLine("var name = commandArgs[i];");
sb.AppendLine();

using (sb.BeginBlock("switch (name)"))
// parse indexed argument([Argument] parameter)
if (hasArgument)
{
// parse argument(fast, switch directly)
for (int i = 0; i < command.Parameters.Length; i++)
{
var parameter = command.Parameters[i];
if (!parameter.IsParsable) continue;
if (parameter.IsArgument) continue;
if (!parameter.IsArgument) continue;

sb.AppendLine($"case \"--{parameter.Name}\":");
foreach (var alias in parameter.Aliases)
{
sb.AppendLine($"case \"{alias}\":");
}
sb.AppendLine($"if (argumentPosition == {parameter.ArgumentIndex})");
using (sb.BeginBlock())
{
sb.AppendLine($"{parameter.BuildParseMethod(i, parameter.Name, increment: true)}");
sb.AppendLine($"{parameter.BuildParseMethod(i, parameter.Name, increment: false)}");
if (parameter.RequireCheckArgumentParsed)
{
sb.AppendLine($"arg{i}Parsed = true;");
}
sb.AppendLine("break;");
}
}

using (sb.BeginIndent("default:"))
{
// parse argument(slow, ignorecase)
for (int i = 0; i < command.Parameters.Length; i++)
{
var parameter = command.Parameters[i];
if (!parameter.IsParsable) continue;
if (parameter.IsArgument) continue;

sb.AppendLine($"if (string.Equals(name, \"--{parameter.Name}\", StringComparison.OrdinalIgnoreCase){(parameter.Aliases.Length == 0 ? ")" : "")}");
for (int j = 0; j < parameter.Aliases.Length; j++)
{
var alias = parameter.Aliases[j];
sb.AppendLine($" || string.Equals(name, \"{alias}\", StringComparison.OrdinalIgnoreCase){(parameter.Aliases.Length == j + 1 ? ")" : "")}");
}
using (sb.BeginBlock())
{
sb.AppendLine($"{parameter.BuildParseMethod(i, parameter.Name, increment: true)}");
if (parameter.RequireCheckArgumentParsed)
{
sb.AppendLine($"arg{i}Parsed = true;");
}
sb.AppendLine($"break;");
}
sb.AppendLine("argumentPosition++;");
sb.AppendLine("continue;");
}

sb.AppendLine("ThrowArgumentNameNotFound(name);");
sb.AppendLine("break;");
}
sb.AppendLine();
}

sb.AppendLine("ThrowArgumentNameNotFound(name);");
}

// validate parsed
Expand Down
54 changes: 54 additions & 0 deletions tests/ConsoleAppFramework.GeneratorTests/RunTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,60 @@ public void SyncRun()
verifier.Execute("ConsoleApp.Run(args, (int x, int y) => { Console.Write((x + y)); });", "--x 10 --y 20", "30");
}

[Fact]
public void OptionTokenShouldNotFillArgumentSlot()
{
var code = """
ConsoleApp.Run(args, ([Argument] string path, bool dryRun) =>
{
Console.Write((dryRun, path).ToString());
});
""";

verifier.Error(code, "--dry-run").ShouldContain("Required argument 'path' was not specified.");
verifier.Execute(code, "--dry-run sample.txt", "(True, sample.txt)");
}

[Fact]
public void OptionTokenAllowsMultipleArguments()
{
var code = """
ConsoleApp.Run(args, ([Argument] string source, [Argument] string destination, bool dryRun) =>
{
Console.Write((dryRun, source, destination).ToString());
});
""";

verifier.Execute(code, "--dry-run input.json output.json", "(True, input.json, output.json)");
}

[Fact]
public void OptionTokenRespectsArgumentDefaultValue()
{
var code = """
ConsoleApp.Run(args, ([Argument] string path = "default-path", bool dryRun = false) =>
{
Console.Write((dryRun, path).ToString());
});
""";

verifier.Execute(code, "--dry-run", "(True, default-path)");
}

[Fact]
public void OptionTokenHandlesParamsArguments()
{
var code = """
ConsoleApp.Run(args, ([Argument] string path, bool dryRun, params string[] extras) =>
{
Console.Write($"{dryRun}:{path}:{string.Join("|", extras)}");
});
""";

verifier.Execute(code, "--dry-run path.txt --extras src.txt dst.txt", "True:path.txt:src.txt|dst.txt");
verifier.Execute(code, "--dry-run path.txt", "True:path.txt:");
}

[Fact]
public void SyncRunShouldFailed()
{
Expand Down