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
15 changes: 15 additions & 0 deletions .idea/.idea.MakeGenericAgain/.idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions .idea/.idea.MakeGenericAgain/.idea/encodings.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/.idea.MakeGenericAgain/.idea/indexLayout.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/.idea.MakeGenericAgain/.idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

120 changes: 61 additions & 59 deletions MakeGenericAgain/CommandLineArgs.cs
Original file line number Diff line number Diff line change
@@ -1,77 +1,79 @@
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
namespace MakeGenericAgain;

namespace MakeGenericAgain
public static class CommandLineArgs
{
public static class CommandLineArgs
public static T Parse<T>(string[] args)
where T : new()
=> Parse<T>(string.Join(" ", args));

public static T Parse<T>(string args)
where T : new()
{
public static T Parse<T>(string[] args) where T : new()
{
return Parse<T>(string.Join(" ", args));
}
T res = new();
PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);

public static T Parse<T>(string args) where T : new()
foreach (PropertyInfo prop in props)
{
var res = new T();
var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance);
IEnumerable<string> namesForProp = (prop.GetCustomAttribute<FromCommandLineAttribute>()?.ParamNames.Select(s => s.EnsureStartsWith("-").ToLower()) ?? [])
.Concat([
"-" + prop.Name.ToLower()
])
.Distinct();

foreach (var prop in props)
foreach (string name in namesForProp)
{
var namesForProp = (prop.GetCustomAttribute<FromCommandLineAttribute>()?.ParamNames.Select(s => s.EnsureStartsWith("-").ToLower()) ?? Enumerable.Empty<string>())
.Concat(new[] { "-" + prop.Name.ToLower() })
.Distinct();
int indexOf = args.IndexOf(name + "=", StringComparison.Ordinal);

if (indexOf < 0)
indexOf = args.IndexOf(name + " ", StringComparison.Ordinal);

if (indexOf < 0)
continue;

int startOfValue = indexOf + name.Length;
if (args[startOfValue] == '=')
startOfValue++;

int endOfValue = args.IndexOf(" -", startOfValue, StringComparison.Ordinal);
if (endOfValue == -1)
endOfValue = args.Length;

string value = args.Substring(startOfValue, endOfValue - startOfValue).Trim();

if (value.StartsWith("\"") && value.EndsWith("\""))
value = value[1..^1];

foreach (var name in namesForProp)
if (IsCollection(prop))
{
var indexOf = args.IndexOf(name + "=", StringComparison.Ordinal);
if (indexOf < 0) indexOf = args.IndexOf(name + " ", StringComparison.Ordinal);
if (indexOf < 0) continue;

var startOfValue = indexOf + name.Length;
if (args[startOfValue] == '=') startOfValue++;

var endOfValue = args.IndexOf(" -", startOfValue, StringComparison.Ordinal);
if (endOfValue == -1) endOfValue = args.Length;

var value = args.Substring(startOfValue, endOfValue - startOfValue).Trim();

if (value.StartsWith("\"") && value.EndsWith("\""))
{
value = value[1..^1];
}

if (IsCollection(prop))
{
var values = value.Split(',')
.Select(v => v.Trim())
.Where(v => !string.IsNullOrEmpty(v))
.ToArray();
prop.SetValue(res, values);
}
else
{
prop.SetValue(res, value);
}
string[] values = value.Split(',')
.Select(v => v.Trim())
.Where(v => !string.IsNullOrEmpty(v))
.ToArray();

prop.SetValue(res, values);
}
else
{
prop.SetValue(res, value);
}
}
return res;
}

return res;
}

private static string EnsureStartsWith(this string str, string toStartWith)
{
if (!str.StartsWith(toStartWith))
str = toStartWith + str;
return str;
}
public static bool IsCollection(this Type type)
=> type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ICollection<>) || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) ||
typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string) || type.IsArray || type.GetInterfaces().Any(IsCollection);

private static bool IsCollection(PropertyInfo prop)
=> prop.PropertyType != typeof(string) && prop.PropertyType.IsCollection();
private static string EnsureStartsWith(this string str, string toStartWith)
{
if (!str.StartsWith(toStartWith))
str = toStartWith + str;

public static bool IsCollection(this Type type)
=> type.IsGenericType && type.GetGenericTypeDefinition() == typeof(ICollection<>) || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>) || typeof(IEnumerable).IsAssignableFrom(type) && type != typeof(string) || type.IsArray || type.GetInterfaces().Any(IsCollection);
return str;
}

private static bool IsCollection(PropertyInfo prop)
=> prop.PropertyType != typeof(string) && prop.PropertyType.IsCollection();
}
17 changes: 6 additions & 11 deletions MakeGenericAgain/FromCommandLineAttribute.cs
Original file line number Diff line number Diff line change
@@ -1,15 +1,10 @@
using System;
namespace MakeGenericAgain;

namespace MakeGenericAgain
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class FromCommandLineAttribute : Attribute
{
[AttributeUsage(AttributeTargets.Property, AllowMultiple = true)]
public class FromCommandLineAttribute: Attribute
{
public FromCommandLineAttribute(params string[] paramNames)
{
ParamNames = paramNames;
}
public FromCommandLineAttribute(params string[] paramNames)
=> ParamNames = paramNames;

public string[] ParamNames { get; set; }
}
public string[] ParamNames { get; set; }
}
7 changes: 7 additions & 0 deletions MakeGenericAgain/GlobalUsings.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
global using System;
global using System.Collections;
global using System.Collections.Generic;
global using System.IO;
global using System.Linq;
global using System.Reflection;
global using System.Text.RegularExpressions;
62 changes: 31 additions & 31 deletions MakeGenericAgain/MakeGenericAgain.csproj
Original file line number Diff line number Diff line change
@@ -1,37 +1,37 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net8.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<PackAsTool>true</PackAsTool>
<ToolCommandName>makeGenericAgain</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<Version>1.0.5</Version>
<Authors>Florian Gilde</Authors>
<PackageId>MakeGenericAgain</PackageId>
<PackageVersion>0.0.0-dev</PackageVersion>
<RepositoryType>Git</RepositoryType>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>
Problem is that nswag client code generation from open api specification or swagger generates classes without generics. This tool can be used afterwards to make classes generic again
</Description>

<RepositoryUrl>https://github.com/fgilde/MakeGenericAgain</RepositoryUrl>
<Copyright>Copyright © $(Authors) 2020-$([System.DateTime]::Now.Year)</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFrameworks>net6.0;net8.0;net10.0</TargetFrameworks>
<LangVersion>latest</LangVersion>
<PackAsTool>true</PackAsTool>
<ToolCommandName>makeGenericAgain</ToolCommandName>
<PackageOutputPath>./nupkg</PackageOutputPath>
<Version>1.0.7</Version>
<Authors>Florian Gilde</Authors>
<PackageId>MakeGenericAgain</PackageId>
<PackageVersion>0.0.0-dev</PackageVersion>
<RepositoryType>Git</RepositoryType>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Description>
Problem is that nswag client code generation from open api specification or swagger generates classes without generics. This tool can be used afterwards to make classes generic again
</Description>

<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.4.231">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>
<RepositoryUrl>https://github.com/fgilde/MakeGenericAgain</RepositoryUrl>
<Copyright>Copyright © $(Authors) 2020-$([System.DateTime]::Now.Year)</Copyright>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="README.md" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Nerdbank.GitVersioning" Version="3.9.50">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
</ItemGroup>

<ItemGroup>
<None Include="..\README.md" Pack="true" PackagePath="README.md" />
</ItemGroup>

</Project>
96 changes: 45 additions & 51 deletions MakeGenericAgain/NameReplacer.cs
Original file line number Diff line number Diff line change
@@ -1,63 +1,57 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
namespace MakeGenericAgain;

namespace MakeGenericAgain
public static class NameReplacer
{
public static class NameReplacer
public static string ReplaceToGeneric(string str, ICollection<string> typesToIgnore)
{
public static string ReplaceToGeneric(string str, ICollection<string> typesToIgnore)
{
var toSplit = Regex.Replace(str, @"[^\w\.@-]", " ", RegexOptions.None, TimeSpan.FromSeconds(1.5)).Replace(".", " ");
foreach (var word in toSplit.Split(" "))
{
// ignore any types provided in options
if (typesToIgnore.Contains(word))
continue;

if (word.Contains("Of") && !word.StartsWith("Of") && !word.EndsWith("Of") && !word.StartsWith("DateTime"))
{
var genericWordFor = GetGenericWordFor(word);
if (word != genericWordFor)
str = str.Replace(word, genericWordFor);
}
}

return str;
}
string toSplit = Regex.Replace(str, @"[^\w\.@-]", " ", RegexOptions.None, TimeSpan.FromSeconds(1.5)).Replace(".", " ");

internal static string GetGenericWordFor(string word)
foreach (string word in toSplit.Split(" "))
{
var parts = word.Split("Of");
// ignore any types provided in options
if (typesToIgnore.Contains(word))
continue;

var closing = string.Join("",Enumerable.Repeat(">", parts.Length - 1));
var opening = string.Join("", parts.Select((p,i) => i < parts.Length - 1 ? $"{TypeNameFor(p)}<" : TypeNameFor(p)));
var result = opening + closing;
if (!word.Contains("Of") || word.StartsWith("Of") || word.EndsWith("Of") || word.StartsWith("DateTime"))
continue;

return result;
string genericWordFor = GetGenericWordFor(word);
if (word != genericWordFor)
str = str.Replace(word, genericWordFor);
}

private static string TypeNameFor(string type)
return str;
}

internal static string GetGenericWordFor(string word)
{
string[] parts = word.Split("Of");

string closing = string.Join("", Enumerable.Repeat(">", parts.Length - 1));

string opening = string.Join("", parts.Select((p, i) => i < parts.Length - 1
? $"{TypeNameFor(p)}<"
: TypeNameFor(p)));

string result = opening + closing;

return result;
}

private static string TypeNameFor(string type)
{
if (type.Contains("And"))
return string.Join(",", type.Split("And").Select(TypeNameFor));

return type switch
{
if (type.Contains("And"))
{
return string.Join(",", type.Split("And").Select(TypeNameFor));
}

if (type == "Integer")
return "int";
if (type == "String")
return "string";
if (type == "Boolean" || type == "Bool")
return "bool";
if (type == "Double")
return "double";
if (type == "Float")
return "float";
if (type == "Decimal")
return "decimal";
return type;
}
"Integer" => "int",
"String" => "string",
"Boolean" or "Bool" => "bool",
"Double" => "double",
"Float" => "float",
"Decimal" => "decimal",
_ => type
};
}
}
Loading