mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
asdad
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using CliFx.SourceGeneration.SemanticModel;
|
||||
using CliFx.SourceGeneration.Utils.Extensions;
|
||||
using Microsoft.CodeAnalysis;
|
||||
using Microsoft.CodeAnalysis.CSharp;
|
||||
@@ -9,7 +9,7 @@ using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||
namespace CliFx.SourceGeneration;
|
||||
|
||||
[Generator]
|
||||
public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
public class CommandSchemaGenerator : IIncrementalGenerator
|
||||
{
|
||||
public void Initialize(IncrementalGeneratorInitializationContext context)
|
||||
{
|
||||
@@ -69,6 +69,13 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
var commandName =
|
||||
commandAttribute.ConstructorArguments.FirstOrDefault().Value as string;
|
||||
|
||||
var commandDescription =
|
||||
commandAttribute
|
||||
.NamedArguments.FirstOrDefault(a =>
|
||||
string.Equals(a.Key, "Description", StringComparison.Ordinal)
|
||||
)
|
||||
.Value.Value as string;
|
||||
|
||||
// Get all parameter inputs
|
||||
var parameterSymbols = namedTypeSymbol
|
||||
.GetMembers()
|
||||
@@ -85,8 +92,9 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
if (parameterAttribute is null)
|
||||
return null;
|
||||
|
||||
var order =
|
||||
parameterAttribute.ConstructorArguments.FirstOrDefault().Value as int?;
|
||||
var isSequence = false; // TODO
|
||||
|
||||
var order = parameterAttribute.ConstructorArguments.First().Value as int?;
|
||||
|
||||
var isRequired =
|
||||
parameterAttribute
|
||||
@@ -94,7 +102,7 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
string.Equals(a.Key, "IsRequired", StringComparison.Ordinal)
|
||||
)
|
||||
.Value.Value as bool?
|
||||
?? p.IsRequired;
|
||||
?? true;
|
||||
|
||||
var name =
|
||||
parameterAttribute
|
||||
@@ -124,19 +132,25 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
.Value.Values.CastArray<ITypeSymbol>();
|
||||
|
||||
return new CommandParameterSymbol(
|
||||
new PropertyInfo(
|
||||
p.Name,
|
||||
p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
new PropertyDescriptor(
|
||||
new TypeDescriptor(
|
||||
p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
),
|
||||
p.Name
|
||||
),
|
||||
isSequence,
|
||||
order,
|
||||
isRequired,
|
||||
name,
|
||||
description,
|
||||
converter?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||
converter
|
||||
?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
?.Pipe(n => new TypeDescriptor(n)),
|
||||
validators
|
||||
.Select(v =>
|
||||
v.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
)
|
||||
.Select(n => new TypeDescriptor(n))
|
||||
.ToArray()
|
||||
);
|
||||
})
|
||||
@@ -159,8 +173,41 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
if (optionAttribute is null)
|
||||
return null;
|
||||
|
||||
var names =
|
||||
optionAttribute.ConstructorArguments.FirstOrDefault().Value as string[];
|
||||
var isSequence = false; // TODO
|
||||
|
||||
var name =
|
||||
optionAttribute
|
||||
.ConstructorArguments.Where(a =>
|
||||
a.Type?.SpecialType == SpecialType.System_String
|
||||
)
|
||||
.Select(a => a.Value)
|
||||
.FirstOrDefault() as string;
|
||||
|
||||
var shortName =
|
||||
optionAttribute
|
||||
.ConstructorArguments.Where(a =>
|
||||
a.Type?.SpecialType == SpecialType.System_Char
|
||||
)
|
||||
.Select(a => a.Value)
|
||||
.FirstOrDefault() as char?;
|
||||
|
||||
var environmentVariable =
|
||||
optionAttribute
|
||||
.NamedArguments.FirstOrDefault(a =>
|
||||
string.Equals(
|
||||
a.Key,
|
||||
"EnvironmentVariable",
|
||||
StringComparison.Ordinal
|
||||
)
|
||||
)
|
||||
.Value.Value as string;
|
||||
|
||||
var isRequired =
|
||||
optionAttribute
|
||||
.NamedArguments.Where(a => a.Key == "IsRequired")
|
||||
.Select(a => a.Value.Value)
|
||||
.FirstOrDefault() as bool?
|
||||
?? p.IsRequired;
|
||||
|
||||
var description =
|
||||
optionAttribute
|
||||
@@ -183,17 +230,26 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
.Value.Values.CastArray<ITypeSymbol>();
|
||||
|
||||
return new CommandOptionSymbol(
|
||||
new PropertyInfo(
|
||||
p.Name,
|
||||
p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
new PropertyDescriptor(
|
||||
new TypeDescriptor(
|
||||
p.Type.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
),
|
||||
p.Name
|
||||
),
|
||||
names,
|
||||
isSequence,
|
||||
name,
|
||||
shortName,
|
||||
environmentVariable,
|
||||
isRequired,
|
||||
description,
|
||||
converter?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||
converter
|
||||
?.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
?.Pipe(n => new TypeDescriptor(n)),
|
||||
validators
|
||||
.Select(v =>
|
||||
v.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat)
|
||||
)
|
||||
.Select(n => new TypeDescriptor(n))
|
||||
.ToArray()
|
||||
);
|
||||
})
|
||||
@@ -202,10 +258,14 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
|
||||
return (
|
||||
new CommandSymbol(
|
||||
namedTypeSymbol.ToDisplayString(SymbolDisplayFormat.FullyQualifiedFormat),
|
||||
new TypeDescriptor(
|
||||
namedTypeSymbol.ToDisplayString(
|
||||
SymbolDisplayFormat.FullyQualifiedFormat
|
||||
)
|
||||
),
|
||||
commandName,
|
||||
parameterSymbols,
|
||||
optionSymbols
|
||||
commandDescription,
|
||||
parameterSymbols.Concat<CommandInputSymbol>(optionSymbols).ToArray()
|
||||
),
|
||||
null
|
||||
);
|
||||
@@ -229,33 +289,38 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
using CliFx.Schema;
|
||||
using CliFx.Extensibility;
|
||||
|
||||
partial class {{ c.TypeName }}
|
||||
namespace {{ c.Type.Namespace }};
|
||||
|
||||
partial class {{ c.Type.Name }}
|
||||
{
|
||||
public static CommandSchema<{{ c.TypeName }}> Schema { get; } = new(
|
||||
public static CommandSchema<{{ c.Type.FullyQualifiedName }}> Schema { get; } = new(
|
||||
{{ c.Name }},
|
||||
{{ c.Description }},
|
||||
[
|
||||
{{ c.Parameters.Select(p =>
|
||||
{{ c.Inputs.Select(i => i switch {
|
||||
CommandParameterSymbol parameter =>
|
||||
// lang=csharp
|
||||
$$"""
|
||||
new CommandParameterSchema<{{ c.TypeName }}, {{ p.Property.TypeName }}>(
|
||||
new PropertyBinding<{{ c.TypeName }}, {{ p.Property.TypeName }}>(
|
||||
obj => obj.{{ p.Property.Name }},
|
||||
(obj, value) => obj.{{ p.Property.Name }} = value
|
||||
new CommandParameterSchema<{{ c.Type.FullyQualifiedName }}, {{ i.Property.Type.FullyQualifiedName }}>(
|
||||
new PropertyBinding<{{ c.Type.FullyQualifiedName }}, {{ i.Property.Type.FullyQualifiedName }}>(
|
||||
obj => obj.{{ i.Property.Name }},
|
||||
(obj, value) => obj.{{ i.Property.Name }} = value
|
||||
),
|
||||
p.Order,
|
||||
p.IsRequired,
|
||||
p.Name,
|
||||
p.Description,
|
||||
new {{ p.ConverterTypeName }}(),
|
||||
new {{ i.ConverterType.FullyQualifiedName }}(),
|
||||
[
|
||||
{{ p.ValidatorTypeNames.Select(v =>
|
||||
{{ i.ValidatorTypes.Select(v =>
|
||||
// lang=csharp
|
||||
$"new {v}()").JoinToString(",\n")
|
||||
$"new {v.FullyQualifiedName}()").JoinToString(",\n")
|
||||
}}
|
||||
]
|
||||
)
|
||||
"""
|
||||
).JoinToString(",\n")
|
||||
""",
|
||||
CommandOptionSymbol option => ""
|
||||
}).JoinToString(",\n")
|
||||
}}
|
||||
]
|
||||
}
|
||||
@@ -266,34 +331,3 @@ public partial class CommandSchemaGenerator : IIncrementalGenerator
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public partial class CommandSchemaGenerator
|
||||
{
|
||||
// TODO make all types structurally equatable
|
||||
private record PropertyInfo(string Name, string TypeName);
|
||||
|
||||
private record CommandParameterSymbol(
|
||||
PropertyInfo Property,
|
||||
int? Order,
|
||||
bool IsRequired,
|
||||
string? Name,
|
||||
string? Description,
|
||||
string? ConverterTypeName,
|
||||
IReadOnlyList<string> ValidatorTypeNames
|
||||
);
|
||||
|
||||
private record CommandOptionSymbol(
|
||||
PropertyInfo Property,
|
||||
string[] Names,
|
||||
string? Description,
|
||||
string? ConverterTypeName,
|
||||
IReadOnlyList<string> ValidatorTypeNames
|
||||
);
|
||||
|
||||
private record CommandSymbol(
|
||||
string TypeName,
|
||||
string? Name,
|
||||
IReadOnlyList<CommandParameterSymbol> Parameters,
|
||||
IReadOnlyList<CommandOptionSymbol> Options
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using Microsoft.CodeAnalysis;
|
||||
using CliFx.SourceGeneration.SemanticModel;
|
||||
using Microsoft.CodeAnalysis;
|
||||
|
||||
namespace CliFx.SourceGeneration;
|
||||
|
||||
|
||||
56
CliFx.SourceGeneration/SemanticModel/CommandInputSymbol.cs
Normal file
56
CliFx.SourceGeneration/SemanticModel/CommandInputSymbol.cs
Normal file
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CliFx.SourceGeneration.SemanticModel;
|
||||
|
||||
internal abstract partial class CommandInputSymbol(
|
||||
PropertyDescriptor property,
|
||||
bool isSequence,
|
||||
string? description,
|
||||
TypeDescriptor? converterType,
|
||||
IReadOnlyList<TypeDescriptor> validatorTypes
|
||||
)
|
||||
{
|
||||
public PropertyDescriptor Property { get; } = property;
|
||||
|
||||
public bool IsSequence { get; } = isSequence;
|
||||
|
||||
public string? Description { get; } = description;
|
||||
|
||||
public TypeDescriptor? ConverterType { get; } = converterType;
|
||||
|
||||
public IReadOnlyList<TypeDescriptor> ValidatorTypes { get; } = validatorTypes;
|
||||
}
|
||||
|
||||
internal partial class CommandInputSymbol : IEquatable<CommandInputSymbol>
|
||||
{
|
||||
public bool Equals(CommandInputSymbol? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return Property.Equals(other.Property)
|
||||
&& IsSequence == other.IsSequence
|
||||
&& Description == other.Description
|
||||
&& Equals(ConverterType, other.ConverterType)
|
||||
&& ValidatorTypes.SequenceEqual(other.ValidatorTypes);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
|
||||
return Equals((CommandInputSymbol)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(Property, IsSequence, Description, ConverterType, ValidatorTypes);
|
||||
}
|
||||
57
CliFx.SourceGeneration/SemanticModel/CommandOptionSymbol.cs
Normal file
57
CliFx.SourceGeneration/SemanticModel/CommandOptionSymbol.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CliFx.SourceGeneration.SemanticModel;
|
||||
|
||||
internal partial class CommandOptionSymbol(
|
||||
PropertyDescriptor property,
|
||||
bool isSequence,
|
||||
string? name,
|
||||
char? shortName,
|
||||
string? environmentVariable,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
TypeDescriptor? converterType,
|
||||
IReadOnlyList<TypeDescriptor> validatorTypes
|
||||
) : CommandInputSymbol(property, isSequence, description, converterType, validatorTypes)
|
||||
{
|
||||
public string? Name { get; } = name;
|
||||
|
||||
public char? ShortName { get; } = shortName;
|
||||
|
||||
public string? EnvironmentVariable { get; } = environmentVariable;
|
||||
|
||||
public bool IsRequired { get; } = isRequired;
|
||||
}
|
||||
|
||||
internal partial class CommandOptionSymbol : IEquatable<CommandOptionSymbol>
|
||||
{
|
||||
public bool Equals(CommandOptionSymbol? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return base.Equals(other)
|
||||
&& Name == other.Name
|
||||
&& ShortName == other.ShortName
|
||||
&& EnvironmentVariable == other.EnvironmentVariable
|
||||
&& IsRequired == other.IsRequired;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
|
||||
return Equals((CommandOptionSymbol)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(base.GetHashCode(), Name, ShortName, EnvironmentVariable, IsRequired);
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CliFx.SourceGeneration.SemanticModel;
|
||||
|
||||
internal partial class CommandParameterSymbol(
|
||||
PropertyDescriptor property,
|
||||
bool isSequence,
|
||||
int order,
|
||||
string name,
|
||||
bool isRequired,
|
||||
string? description,
|
||||
TypeDescriptor? converterType,
|
||||
IReadOnlyList<TypeDescriptor> validatorTypes
|
||||
) : CommandInputSymbol(property, isSequence, description, converterType, validatorTypes)
|
||||
{
|
||||
public int Order { get; } = order;
|
||||
|
||||
public string Name { get; } = name;
|
||||
|
||||
public bool IsRequired { get; } = isRequired;
|
||||
}
|
||||
|
||||
internal partial class CommandParameterSymbol : IEquatable<CommandParameterSymbol>
|
||||
{
|
||||
public bool Equals(CommandParameterSymbol? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return base.Equals(other)
|
||||
&& Order == other.Order
|
||||
&& Name == other.Name
|
||||
&& IsRequired == other.IsRequired;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
|
||||
return Equals((CommandParameterSymbol)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() =>
|
||||
HashCode.Combine(base.GetHashCode(), Order, Name, IsRequired);
|
||||
}
|
||||
57
CliFx.SourceGeneration/SemanticModel/CommandSymbol.cs
Normal file
57
CliFx.SourceGeneration/SemanticModel/CommandSymbol.cs
Normal file
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
|
||||
namespace CliFx.SourceGeneration.SemanticModel;
|
||||
|
||||
internal partial class CommandSymbol(
|
||||
TypeDescriptor type,
|
||||
string? name,
|
||||
string? description,
|
||||
IReadOnlyList<CommandInputSymbol> inputs
|
||||
)
|
||||
{
|
||||
public TypeDescriptor Type { get; } = type;
|
||||
|
||||
public string? Name { get; } = name;
|
||||
|
||||
public string? Description { get; } = description;
|
||||
|
||||
public IReadOnlyList<CommandInputSymbol> Inputs { get; } = inputs;
|
||||
|
||||
public IReadOnlyList<CommandParameterSymbol> Parameters =>
|
||||
Inputs.OfType<CommandParameterSymbol>().ToArray();
|
||||
|
||||
public IReadOnlyList<CommandOptionSymbol> Options =>
|
||||
Inputs.OfType<CommandOptionSymbol>().ToArray();
|
||||
}
|
||||
|
||||
internal partial class CommandSymbol : IEquatable<CommandSymbol>
|
||||
{
|
||||
public bool Equals(CommandSymbol? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return Type.Equals(other.Type)
|
||||
&& Name == other.Name
|
||||
&& Description == other.Description
|
||||
&& Inputs.SequenceEqual(other.Inputs);
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
|
||||
return Equals((CommandSymbol)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Type, Name, Description, Inputs);
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace CliFx.SourceGeneration;
|
||||
namespace CliFx.SourceGeneration.SemanticModel;
|
||||
|
||||
internal static class KnownSymbolNames
|
||||
{
|
||||
37
CliFx.SourceGeneration/SemanticModel/PropertyDescriptor.cs
Normal file
37
CliFx.SourceGeneration/SemanticModel/PropertyDescriptor.cs
Normal file
@@ -0,0 +1,37 @@
|
||||
using System;
|
||||
|
||||
namespace CliFx.SourceGeneration.SemanticModel;
|
||||
|
||||
internal partial class PropertyDescriptor(TypeDescriptor type, string name)
|
||||
{
|
||||
public TypeDescriptor Type { get; } = type;
|
||||
|
||||
public string Name { get; } = name;
|
||||
}
|
||||
|
||||
internal partial class PropertyDescriptor : IEquatable<PropertyDescriptor>
|
||||
{
|
||||
public bool Equals(PropertyDescriptor? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return Type.Equals(other.Type) && Name == other.Name;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
|
||||
return Equals((PropertyDescriptor)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => HashCode.Combine(Type, Name);
|
||||
}
|
||||
40
CliFx.SourceGeneration/SemanticModel/TypeDescriptor.cs
Normal file
40
CliFx.SourceGeneration/SemanticModel/TypeDescriptor.cs
Normal file
@@ -0,0 +1,40 @@
|
||||
using System;
|
||||
using CliFx.SourceGeneration.Utils.Extensions;
|
||||
|
||||
namespace CliFx.SourceGeneration.SemanticModel;
|
||||
|
||||
internal partial class TypeDescriptor(string fullyQualifiedName)
|
||||
{
|
||||
public string FullyQualifiedName { get; } = fullyQualifiedName;
|
||||
|
||||
public string Namespace { get; } = fullyQualifiedName.SubstringUntilLast(".");
|
||||
|
||||
public string Name { get; } = fullyQualifiedName.SubstringAfterLast(".");
|
||||
}
|
||||
|
||||
internal partial class TypeDescriptor : IEquatable<TypeDescriptor>
|
||||
{
|
||||
public bool Equals(TypeDescriptor? other)
|
||||
{
|
||||
if (ReferenceEquals(null, other))
|
||||
return false;
|
||||
if (ReferenceEquals(this, other))
|
||||
return true;
|
||||
|
||||
return FullyQualifiedName == other.FullyQualifiedName;
|
||||
}
|
||||
|
||||
public override bool Equals(object? obj)
|
||||
{
|
||||
if (ReferenceEquals(null, obj))
|
||||
return false;
|
||||
if (ReferenceEquals(this, obj))
|
||||
return true;
|
||||
if (obj.GetType() != GetType())
|
||||
return false;
|
||||
|
||||
return Equals((TypeDescriptor)obj);
|
||||
}
|
||||
|
||||
public override int GetHashCode() => FullyQualifiedName.GetHashCode();
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
using System;
|
||||
|
||||
namespace CliFx.SourceGeneration.Utils.Extensions;
|
||||
|
||||
internal static class GenericExtensions
|
||||
{
|
||||
public static TOut Pipe<TIn, TOut>(this TIn input, Func<TIn, TOut> transform) =>
|
||||
transform(input);
|
||||
}
|
||||
@@ -1,9 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace CliFx.SourceGeneration.Utils.Extensions;
|
||||
|
||||
internal static class StringExtensions
|
||||
{
|
||||
public static string SubstringUntilLast(
|
||||
this string str,
|
||||
string sub,
|
||||
StringComparison comparison = StringComparison.Ordinal
|
||||
)
|
||||
{
|
||||
var index = str.LastIndexOf(sub, comparison);
|
||||
return index < 0 ? str : str[..index];
|
||||
}
|
||||
|
||||
public static string SubstringAfterLast(
|
||||
this string str,
|
||||
string sub,
|
||||
StringComparison comparison = StringComparison.Ordinal
|
||||
)
|
||||
{
|
||||
var index = str.LastIndexOf(sub, comparison);
|
||||
return index >= 0 ? str.Substring(index + sub.Length, str.Length - index - sub.Length) : "";
|
||||
}
|
||||
|
||||
public static string JoinToString<T>(this IEnumerable<T> source, string separator) =>
|
||||
string.Join(separator, source);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user