Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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: 3 additions & 0 deletions samples/DotNetCampus.CommandLine.Sample/DefaultOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ namespace DotNetCampus.Cli;

internal class DefaultOptions
{
[RawArguments]
public required string[] MainArgs { get; init; }

[Option(LocalizableDescription = nameof(LocalizableStrings.SamplePropertyDescription))]
public string? DefaultText { get; set; }

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,8 +64,10 @@ private string GenerateCommandObjectCreatorCode(CommandObjectGeneratingModel mod
// | 0 | 1 | 1 | 赋值 |
// | 1 | 1 | 1 | 赋值 |

var initRawArgumentsProperties = model.RawArgumentsProperties.Where(x => x.IsRequired || x.IsInitOnly).ToImmutableArray();
var initOptionProperties = model.OptionProperties.Where(x => x.IsRequired || x.IsInitOnly).ToImmutableArray();
var initValueProperties = model.ValueProperties.Where(x => x.IsRequired || x.IsInitOnly).ToImmutableArray();
var setRawArgumentsProperties = model.RawArgumentsProperties.Where(x => !x.IsRequired && !x.IsInitOnly).ToImmutableArray();
var setOptionProperties = model.OptionProperties.Where(x => !x.IsRequired && !x.IsInitOnly).ToImmutableArray();
var setValueProperties = model.ValueProperties.Where(x => !x.IsRequired && !x.IsInitOnly).ToImmutableArray();
return $$"""
Expand All @@ -82,11 +84,25 @@ public static object CreateInstance(global::DotNetCampus.Cli.CommandLine command
var caseSensitive = commandLine.DefaultCaseSensitive;
var result = new {{model.CommandObjectType.ToGlobalDisplayString()}}
{
{{(initOptionProperties.Length is 0 ? " // There is no option to be initialized." : string.Join("\n", initOptionProperties.Select(GenerateOptionPropertyAssignment)))}}
{{(initValueProperties.Length is 0 ? " // There is no positional argument to be initialized." : string.Join("\n", initValueProperties.Select((x, i) => GenerateValuePropertyAssignment(model, x, i))))}}
// 1. [RawArguments]
{{(initRawArgumentsProperties.Length is 0 ? " // MainArgs = commandLine.CommandLineArguments," : string.Join("\n", initRawArgumentsProperties.Select(GenerateRawArgumentsPropertyAssignment)))}}

// 2. [Option]
{{(initOptionProperties .Length is 0 ? " // There is no option to be initialized." : string.Join("\n", initOptionProperties.Select(GenerateOptionPropertyAssignment)))}}

// 3. [Value]
{{(initValueProperties .Length is 0 ? " // There is no positional argument to be initialized." : string.Join("\n", initValueProperties.Select((x, i) => GenerateValuePropertyAssignment(model, x, i))))}}
};
{{(setOptionProperties.Length is 0 ? " // There is no option to be assigned." : string.Join("\n", setOptionProperties.Select(GenerateOptionPropertyAssignment)))}}
{{(setValueProperties.Length is 0 ? " // There is no positional argument to be assigned." : string.Join("\n", setValueProperties.Select((x, i) => GenerateValuePropertyAssignment(model, x, i))))}}

// 1. [RawArguments]
{{(setRawArgumentsProperties.Length is 0 ? " // result.MainArgs = commandLine.CommandLineArguments;" : string.Join("\n", setRawArgumentsProperties.Select(GenerateRawArgumentsPropertyAssignment)))}}

// 2. [Option]
{{(setOptionProperties .Length is 0 ? " // There is no option to be assigned." : string.Join("\n", setOptionProperties.Select(GenerateOptionPropertyAssignment)))}}

// 3. [Value]
{{(setValueProperties .Length is 0 ? " // There is no positional argument to be assigned." : string.Join("\n", setValueProperties.Select((x, i) => GenerateValuePropertyAssignment(model, x, i))))}}

return result;
}
}
Expand Down Expand Up @@ -182,6 +198,23 @@ private string GenerateValuePropertyAssignment(CommandObjectGeneratingModel mode
}
}

private string GenerateRawArgumentsPropertyAssignment(RawArgumentsPropertyGeneratingModel property, int modelIndex)
{
var isInitProperty = property.IsRequired || property.IsInitOnly;
if (isInitProperty)
{
return $"""
{property.PropertyName} = ({property.Type.ToDisplayString()})commandLine.CommandLineArguments,
""";
}
else
{
return $$"""
result.{{property.PropertyName}} = ({{property.Type.ToDisplayString()}})commandLine.CommandLineArguments;
""";
}
}

/// <summary>
/// 获取一个方法名,调用该方法可使“命令行属性值”转换为“目标类型”。
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ public static IncrementalValuesProvider<CommandObjectGeneratingModel> SelectComm
// 3. 拥有 [Verb] 特性
// 4. 拥有 [Option] 特性的属性
// 5. 拥有 [Value] 特性的属性
// 6. 拥有 [RawArguments] 特性的属性

// 1. 实现 ICommandOptions 接口。
var isOptions = typeSymbol.AllInterfaces.Any(i =>
Expand All @@ -52,26 +53,13 @@ public static IncrementalValuesProvider<CommandObjectGeneratingModel> SelectComm
.FirstOrDefault(a => a.AttributeClass!.IsAttributeOf<VerbAttribute>());
// 4. 拥有 [Option] 特性的属性。
var optionProperties = typeSymbol
.EnumerateBaseTypesRecursively() // 递归获取所有基类
.Reverse() // (注意我们先给父类属性赋值,再给子类属性赋值)
.SelectMany(x => x.GetMembers()) // 的所有成员,
.OfType<IPropertySymbol>() // 然后取出属性,
.Select(OptionPropertyGeneratingModel.TryParse) // 解析出 OptionPropertyGeneratingModel。
.OfType<OptionPropertyGeneratingModel>()
.GroupBy(x => x.PropertyName) // 按属性名去重。
.Select(x => x.Last()) // 随后,取子类的属性(去除父类的重名属性)。
.ToImmutableArray();
.GetAttributedProperties(OptionPropertyGeneratingModel.TryParse);
// 5. 拥有 [Value] 特性的属性。
var valueProperties = typeSymbol
.EnumerateBaseTypesRecursively() // 递归获取所有基类
.Reverse() // (注意我们先给父类属性赋值,再给子类属性赋值)
.SelectMany(x => x.GetMembers()) // 的所有成员,
.OfType<IPropertySymbol>() // 然后取出属性,
.Select(ValuePropertyGeneratingModel.TryParse) // 解析出 ValuePropertyGeneratingModel。
.OfType<ValuePropertyGeneratingModel>()
.GroupBy(x => x.PropertyName) // 按属性名去重。
.Select(x => x.Last()) // 随后,取子类的属性(去除父类的重名属性)。
.ToImmutableArray();
.GetAttributedProperties(ValuePropertyGeneratingModel.TryParse);
// 6. 拥有 [RawArguments] 特性的属性。
var rawArgumentsProperties = typeSymbol
.GetAttributedProperties(RawArgumentsPropertyGeneratingModel.TryParse);

if (!isOptions && !isHandler && attribute is null && optionProperties.IsEmpty && valueProperties.IsEmpty)
{
Expand All @@ -90,6 +78,7 @@ public static IncrementalValuesProvider<CommandObjectGeneratingModel> SelectComm
IsHandler = isHandler,
OptionProperties = optionProperties,
ValueProperties = valueProperties,
RawArgumentsProperties = rawArgumentsProperties,
};
})
.Where(m => m is not null)
Expand Down Expand Up @@ -140,6 +129,8 @@ internal record CommandObjectGeneratingModel

public required ImmutableArray<ValuePropertyGeneratingModel> ValueProperties { get; init; }

public required ImmutableArray<RawArgumentsPropertyGeneratingModel> RawArgumentsProperties { get; init; }

public string GetBuilderTypeName() => GetBuilderTypeName(CommandObjectType);

public static string GetBuilderTypeName(INamedTypeSymbol commandObjectType)
Expand Down Expand Up @@ -334,6 +325,37 @@ internal record ValuePropertyGeneratingModel
}
}

internal record RawArgumentsPropertyGeneratingModel
{
public required string PropertyName { get; init; }

public required ITypeSymbol Type { get; init; }

public required bool IsRequired { get; init; }

public required bool IsInitOnly { get; init; }

public required bool IsNullable { get; init; }

public static RawArgumentsPropertyGeneratingModel? TryParse(IPropertySymbol propertySymbol)
{
var rawArgumentsAttribute = propertySymbol.GetAttributes().FirstOrDefault(a => a.AttributeClass!.IsAttributeOf<RawArgumentsAttribute>());
if (rawArgumentsAttribute is null)
{
return null;
}

return new RawArgumentsPropertyGeneratingModel
{
PropertyName = propertySymbol.Name,
Type = propertySymbol.Type,
IsRequired = propertySymbol.IsRequired,
IsInitOnly = propertySymbol.SetMethod?.IsInitOnly ?? false,
IsNullable = propertySymbol.Type.NullableAnnotation == NullableAnnotation.Annotated,
};
}
}

internal record AssemblyCommandsGeneratingModel
{
public required string Namespace { get; init; }
Expand All @@ -352,4 +374,21 @@ public static IEnumerable<ITypeSymbol> EnumerateBaseTypesRecursively(this ITypeS
current = current.BaseType;
}
}

public static ImmutableArray<TModel> GetAttributedProperties<TModel>(this ITypeSymbol typeSymbol,
Func<IPropertySymbol, TModel?> propertyParser)
where TModel : class
{
return typeSymbol
.EnumerateBaseTypesRecursively() // 递归获取所有基类
.Reverse() // (注意我们先给父类属性赋值,再给子类属性赋值)
.SelectMany(x => x.GetMembers()) // 的所有成员,
.OfType<IPropertySymbol>() // 然后取出属性,
.Select(x => (PropertyName: x.Name, Model: propertyParser(x))) // 解析出 OptionPropertyGeneratingModel。
.Where(x => x.Model is not null)
.GroupBy(x => x.PropertyName) // 按属性名去重。
.Select(x => x.Last().Model) // 随后,取子类的属性(去除父类的重名属性)。
.Cast<TModel>()
.ToImmutableArray();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace DotNetCampus.Cli.Compiler;

/// <summary>
/// 标记在一个 string[] 或 IReadOnlyList&lt;string&gt; 类型的属性上,表示此属性将接收保留的原始命令行参数。
/// </summary>
[AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = false)]
public sealed class RawArgumentsAttribute : CommandLineAttribute
{
}
Loading