Skip to content
Merged
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
3 changes: 2 additions & 1 deletion samples/DotNetCampus.CommandLine.Sample/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ static void Main(string[] args)
stopwatch.Restart();
for (var i = 0; i < testCount; i++)
{
_ = newCommandLine.As<Options>(OptionsBuilder.CreateInstance);
var context = new CommandRunningContext { CommandLine = newCommandLine };
_ = new OptionsBuilder().Build(context);
}
stopwatch.Stop();
Console.Write($"{stopwatch.ElapsedMilliseconds.ToString(),7} ms | ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ private void GenerateCommandLineAddHandlerCode(TypeDeclarationSourceTextBuilder
.AddTypeConstraints("where T : class, global::DotNetCampus.Cli.ICommandHandler")
.AddRawStatement(GenerateComment(model))
.AddRawStatements($"""
return commandLine.AsRunner().AddHandler<T>(global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CreateInstance);
return commandLine.AsRunner().AddHandler<T>(global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, new global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.Metadata());
"""));
}

Expand All @@ -166,7 +166,7 @@ private void GenerateCommandLineAddHandlerActionCode(TypeDeclarationSourceTextBu
.AddTypeConstraints("where T : class")
.AddRawStatement(GenerateComment(model))
.AddRawStatements($"""
return commandLine.AsRunner().AddHandler<T>(handler, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CreateInstance);
return commandLine.AsRunner().AddHandler<T>(handler, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, new global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.Metadata());
"""));
}

Expand Down Expand Up @@ -293,7 +293,7 @@ private void GenerateCommandBuilderAddHandlerCode(TypeDeclarationSourceTextBuild
.AddTypeConstraints("where T : class, global::DotNetCampus.Cli.ICommandHandler")
.AddRawStatement(GenerateComment(model))
.AddRawStatements($"""
return global::DotNetCampus.Cli.CommandRunnerBuilderExtensions.AddHandler<T>(builder, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CreateInstance);
return global::DotNetCampus.Cli.CommandRunnerBuilderExtensions.AddHandler<T>(builder, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, new global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.Metadata());
"""));
}

Expand All @@ -310,7 +310,7 @@ private void GenerateStatedCommandBuilderAddHandlerCode(TypeDeclarationSourceTex
.AddTypeConstraints("where T : class, global::DotNetCampus.Cli.ICommandHandler<TState>")
.AddRawStatement(GenerateComment(model))
.AddRawStatements($"""
return builder.AddHandler<T>(global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CreateInstance);
return builder.AddHandler<T>(global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, new global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.Metadata());
"""));
}

Expand All @@ -327,7 +327,7 @@ private void GenerateCommandBuilderAddHandlerActionCode(TypeDeclarationSourceTex
.AddTypeConstraints("where T : class")
.AddRawStatement(GenerateComment(model))
.AddRawStatements($"""
return global::DotNetCampus.Cli.CommandRunnerBuilderExtensions.AddHandler<T>(builder, handler, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CreateInstance);
return global::DotNetCampus.Cli.CommandRunnerBuilderExtensions.AddHandler<T>(builder, handler, global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.CommandNameGroup, new global::{model.CommandObjectType.ContainingNamespace}.{model.GetBuilderTypeName()}.Metadata());
"""));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
using System.Text;
using DotNetCampus.CommandLine.CodeAnalysis;
using DotNetCampus.CommandLine.Generators.Builders;
using DotNetCampus.CommandLine.Generators.ModelProviding;
Expand Down Expand Up @@ -29,15 +28,6 @@ private void Execute(SourceProductionContext context, CommandObjectGeneratingMod

var code = GenerateCommandObjectCreatorCode(model);
context.AddSource($"CommandLine.Models/{model.CommandObjectType.ToDisplayString()}.cs", code);

// if (model.UseFullStackParser)
// {
// var originalCode = EmbeddedSourceFiles.Enumerate(null)
// .First(x => x.FileName == "CommandLineParser.cs")
// .Content;
// var parserCode = GenerateParserCode(originalCode, model);
// context.AddSource($"CommandLine.Models/{model.Namespace}.{model.CommandObjectType.Name}.parser.cs", parserCode);
// }
}

private static bool ReportDiagnostics(SourceProductionContext context, CommandObjectGeneratingModel model)
Expand Down Expand Up @@ -74,24 +64,21 @@ private string GenerateCommandObjectCreatorCode(CommandObjectGeneratingModel mod
.AddTypeDeclaration(GenerateBuilderTypeDeclarationLine(model), t => t
.WithSummaryComment($"""辅助 <see cref="{model.CommandObjectType.ToUsingString()}"/> 生成命令行选项、子命令或处理函数的创建。""")
.AddRawMembers(GenerateCommandNames(model))
.AddMethodDeclaration(
$"public static {model.CommandObjectType.ToUsingString()} CreateInstance(global::DotNetCampus.Cli.Compiler.CommandRunningContext context)",
m => m
.AddRawStatements($"return new {model.Namespace}.{model.GetBuilderTypeName()}().Build(context);"))
.AddTypeDeclaration(GenerateMetadataTypeDeclarationLine(model), nt => GenerateCommandObjectMetadata(nt, model))
.AddRawMembers(model.OptionProperties.Select(GenerateArgumentPropertyCode))
.AddRawMembers(model.EnumeratePositionalArgumentExcludingSameNameOptions().Select(GenerateArgumentPropertyCode))
.AddRawText(GenerateBuildCode(model))
.AddMethodDeclaration(
"private global::DotNetCampus.Cli.Utils.Parsers.OptionValueMatch MatchLongOption(ReadOnlySpan<char> longOption, bool defaultCaseSensitive, global::DotNetCampus.Cli.CommandNamingPolicy namingPolicy)",
"public global::DotNetCampus.Cli.Utils.Parsers.OptionValueMatch MatchLongOption(ReadOnlySpan<char> longOption, bool defaultCaseSensitive, global::DotNetCampus.Cli.CommandNamingPolicy namingPolicy)",
m => GenerateMatchLongOptionCode(m, model))
.AddMethodDeclaration(
"private global::DotNetCampus.Cli.Utils.Parsers.OptionValueMatch MatchShortOption(ReadOnlySpan<char> shortOption, bool defaultCaseSensitive)",
"public global::DotNetCampus.Cli.Utils.Parsers.OptionValueMatch MatchShortOption(ReadOnlySpan<char> shortOption, bool defaultCaseSensitive)",
m => GenerateMatchShortOptionCode(m, model))
.AddMethodDeclaration(
"private global::DotNetCampus.Cli.Utils.Parsers.PositionalArgumentValueMatch MatchPositionalArguments(ReadOnlySpan<char> value, int argumentIndex)",
"public global::DotNetCampus.Cli.Utils.Parsers.PositionalArgumentValueMatch MatchPositionalArguments(ReadOnlySpan<char> value, int argumentIndex)",
m => GenerateMatchPositionalArgumentsCode(m, model))
.AddMethodDeclaration(
"private void AssignPropertyValue(string propertyName, int propertyIndex, ReadOnlySpan<char> key, ReadOnlySpan<char> value)",
"public void AssignPropertyValue(string propertyName, int propertyIndex, ReadOnlySpan<char> key, ReadOnlySpan<char> value)",
m => m
.Condition(model.OptionProperties.Count > 0 || model.PositionalArgumentProperties.Count > 0, b => b
.AddBracketScope("switch (propertyIndex)", l => l
Expand All @@ -114,8 +101,14 @@ private static string GenerateBuilderTypeDeclarationLine(CommandObjectGenerating
{
var modifier = model.IsPublic ? "public" : "internal";
return model.UseFullStackParser
? $"{modifier} partial struct {model.GetBuilderTypeName()}()"
: $"{modifier} sealed class {model.GetBuilderTypeName()}";
? $"{modifier} partial struct {model.GetBuilderTypeName()}() : global::DotNetCampus.Cli.Compiler.ICommandObjectBuilder // 临时继承,后面要去掉"
: $"{modifier} sealed class {model.GetBuilderTypeName()} : global::DotNetCampus.Cli.Compiler.ICommandObjectBuilder";
}

private static string GenerateMetadataTypeDeclarationLine(CommandObjectGeneratingModel model)
{
var modifier = model.IsPublic ? "public" : "internal";
return $"{modifier} sealed class Metadata : global::DotNetCampus.Cli.Compiler.ICommandObjectMetadata";
}

private static string GenerateCommandNames(CommandObjectGeneratingModel model)
Expand All @@ -130,6 +123,13 @@ private static string GenerateCommandNames(CommandObjectGeneratingModel model)
""";
}

private void GenerateCommandObjectMetadata(TypeDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
{
builder
.AddMethodDeclaration("public object Build(global::DotNetCampus.Cli.Compiler.CommandRunningContext context)", m => m
.AddRawStatement($"return new {model.Namespace}.{model.GetBuilderTypeName()}().Build(context);"));
}

private string GenerateArgumentPropertyCode(PropertyGeneratingModel model) =>
$"private {GetArgumentPropertyTypeName(model)} {model.PropertyName} = new();";

Expand All @@ -152,22 +152,16 @@ private static string GenerateBuildCode(CommandObjectGeneratingModel model) => $
return BuildDefault(context.CommandLine);
}

var parser = new global::DotNetCampus.Cli.Utils.Parsers.CommandLineParser(context.CommandLine, "{{model.CommandObjectType.Name}}", {{model.GetCommandLevel()}})
{
MatchLongOption = MatchLongOption,
MatchShortOption = MatchShortOption,
MatchPositionalArguments = MatchPositionalArguments,
AssignPropertyValue = AssignPropertyValue,
};
var parser = new global::DotNetCampus.Cli.Utils.Parsers.CommandLineParser(context.CommandLine, this, "{{model.CommandObjectType.Name}}", {{model.GetCommandLevel()}});
parser.Parse().WithFallback(context);
return BuildCore(context.CommandLine);
}
""";

private MethodDeclarationSourceTextBuilder GenerateMatchLongOptionCode(MethodDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
private void GenerateMatchLongOptionCode(MethodDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
{
var optionProperties = model.OptionProperties;
return builder
builder
.Condition(optionProperties.Count is 0, b => b
.AddRawStatement("// 没有长名称选项,无需匹配。"))
.Otherwise(b => b
Expand Down Expand Up @@ -208,11 +202,11 @@ static string GenerateLongOptionEqualsCode(OptionalArgumentPropertyGeneratingMod
}
}

private MethodDeclarationSourceTextBuilder GenerateMatchShortOptionCode(MethodDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
private void GenerateMatchShortOptionCode(MethodDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
{
var optionProperties = model.OptionProperties;
var hasShortName = optionProperties.SelectMany(x => x.GetShortNames()).Any();
return builder
builder
.Condition(!hasShortName, b => b
.AddRawStatement("// 没有短名称选项,无需匹配。"))
.Otherwise(b => b
Expand Down Expand Up @@ -259,12 +253,12 @@ static string GenerateOptionEqualsCode(OptionalArgumentPropertyGeneratingModel m
}
}

private MethodDeclarationSourceTextBuilder GenerateMatchPositionalArgumentsCode(MethodDeclarationSourceTextBuilder builder,
private void GenerateMatchPositionalArgumentsCode(MethodDeclarationSourceTextBuilder builder,
CommandObjectGeneratingModel model)
{
var positionalArgumentProperties = model.PositionalArgumentProperties;
var matchAllProperty = positionalArgumentProperties.FirstOrDefault(x => x.Index is 0 && x.Length is int.MaxValue);
return builder
builder
.Condition(positionalArgumentProperties.Count is 0, b => b
.AddRawStatement("// 没有位置参数,无需匹配。")
.AddRawStatement("return global::DotNetCampus.Cli.Utils.Parsers.PositionalArgumentValueMatch.NotMatch;"))
Expand Down Expand Up @@ -328,7 +322,7 @@ private string GenerateAssignPropertyValueCode(PropertyGeneratingModel model)
""";
}

private MethodDeclarationSourceTextBuilder GenerateBuildCoreCode(MethodDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
private void GenerateBuildCoreCode(MethodDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
{
var initRawArgumentsProperties = model.RawArgumentsProperties.Where(x => x.IsRequiredOrInit).ToList();
var initOptionProperties = model.OptionProperties.Where(x => x.IsRequiredOrInit).ToList();
Expand All @@ -337,7 +331,7 @@ private MethodDeclarationSourceTextBuilder GenerateBuildCoreCode(MethodDeclarati
var setOptionProperties = model.OptionProperties.Where(x => !x.IsRequiredOrInit).ToList();
var setPositionalArgumentProperties = model.PositionalArgumentProperties.Where(x => !x.IsRequiredOrInit).ToList();

return builder
builder
.AddBracketScope($"var result = new {model.CommandObjectType.ToUsingString()}", "{", "};", c => c

// 1. [RawArguments]
Expand Down Expand Up @@ -398,7 +392,7 @@ private MethodDeclarationSourceTextBuilder GenerateBuildCoreCode(MethodDeclarati
.AddRawStatement("return result;");
}

private MethodDeclarationSourceTextBuilder GenerateBuildDefaultCode(MethodDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
private void GenerateBuildDefaultCode(MethodDeclarationSourceTextBuilder builder, CommandObjectGeneratingModel model)
{
var initRawArgumentsProperties = model.RawArgumentsProperties.Where(x => x.IsRequiredOrInit).ToList();
var initOptionProperties = model.OptionProperties.Where(x => x.IsRequiredOrInit).ToList();
Expand All @@ -411,10 +405,10 @@ private MethodDeclarationSourceTextBuilder GenerateBuildDefaultCode(MethodDeclar
builder.AddRawStatement("""
throw new global::DotNetCampus.Cli.Exceptions.RequiredPropertyNotAssignedException($"The command line arguments doesn't contain any required option or positional argument. Command line: {commandLine}", null!);
""");
return builder;
return;
}

return builder
builder
.AddBracketScope($"var result = new {model.CommandObjectType.ToUsingString()}", "{", "};", c => c

// 1. [RawArguments]
Expand Down Expand Up @@ -570,14 +564,6 @@ private string GenerateEnumDeclarationCode(ITypeSymbol enumType)
}
""";
}

private string GenerateParserCode(string originalCode, CommandObjectGeneratingModel model) => new StringBuilder()
.AppendLine("#nullable enable")
.AppendLine("using DotNetCampus.Cli;")
.Append(originalCode)
.Replace("namespace DotNetCampus.Cli.Utils.Parsers;", $"namespace {model.Namespace};")
.Replace("public readonly ref struct CommandLineParser", $"partial struct {model.GetBuilderTypeName()}")
.ToString();
}

file static class Extensions
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@ internal record CommandObjectGeneratingModel

public required IReadOnlyList<PositionalArgumentPropertyGeneratingModel> PositionalArgumentProperties { get; init; }

public string GetBuilderTypeName() => GetBuilderTypeName(CommandObjectType);
public string GetBuilderTypeName()
{
return GetBuilderTypeName(CommandObjectType);
}

public static string GetBuilderTypeName(INamedTypeSymbol commandObjectType)
{
Expand Down
12 changes: 0 additions & 12 deletions src/DotNetCampus.CommandLine/CommandLine.cs
Original file line number Diff line number Diff line change
Expand Up @@ -102,18 +102,6 @@ public static CommandLine Parse(string singleLineCommandLineArgs, CommandLinePar
public T As<T>() where T : notnull => throw MethodShouldBeInspected();
#pragma warning restore CA1822

/// <summary>
/// 尝试将命令行参数转换为指定类型的实例。
/// </summary>
/// <param name="factory">由拦截器传入的命令处理器创建方法。</param>
/// <typeparam name="T">要转换的类型。</typeparam>
/// <returns>转换后的实例。</returns>
[Pure, EditorBrowsable(EditorBrowsableState.Never)]
public T As<T>(CommandObjectFactory factory) where T : notnull
{
return (T)factory(new CommandRunningContext { CommandLine = this });
}

/// <summary>
/// 输出传入的命令行参数字符串。如果命令行参数中传入的是 URL,此方法会将 URL 转换为普通的命令行参数再输出。
/// </summary>
Expand Down
16 changes: 15 additions & 1 deletion src/DotNetCampus.CommandLine/CommandLineExceptionHandler.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using DotNetCampus.Cli.Compiler;
using DotNetCampus.Cli.Utils.Parsers;

namespace DotNetCampus.Cli;
Expand All @@ -20,6 +21,19 @@ public Task<int> RunAsync()
}
}

internal sealed class CommandLineExceptionHandlerMetadata(bool ignoreAllExceptions) : ICommandObjectMetadata
{
public object Build(CommandRunningContext context)
{
return new CommandLineExceptionHandler(context.CommandLine, ignoreAllExceptions);
}

public Task<int> RunAsync(object createdCommandObject)
{
return ((CommandLineExceptionHandler)createdCommandObject).RunAsync();
}
}

/// <summary>
/// 辅助创建命令行异常处理器。
/// </summary>
Expand All @@ -34,6 +48,6 @@ public static class CommandLineExceptionHandlerExtensions
[Obsolete("此方法的实现正在讨论中,API 可能不稳定,请谨慎使用。")]
public static IAsyncCommandRunnerBuilder HandleException(this ICoreCommandRunnerBuilder builder, bool ignoreAllExceptions)
{
return builder.AsRunner().AddFallbackHandler(c => new CommandLineExceptionHandler(c.CommandLine, ignoreAllExceptions));
return builder.AsRunner().AddFallbackHandler(new CommandLineExceptionHandlerMetadata(ignoreAllExceptions));
}
}
Loading