diff --git a/.idea/.idea.MakeGenericAgain/.idea/.gitignore b/.idea/.idea.MakeGenericAgain/.idea/.gitignore new file mode 100644 index 0000000..c69e223 --- /dev/null +++ b/.idea/.idea.MakeGenericAgain/.idea/.gitignore @@ -0,0 +1,15 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Rider ignored files +/projectSettingsUpdater.xml +/modules.xml +/.idea.MakeGenericAgain.iml +/contentModel.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Ignored default folder with query files +/queries/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.idea.MakeGenericAgain/.idea/encodings.xml b/.idea/.idea.MakeGenericAgain/.idea/encodings.xml new file mode 100644 index 0000000..df87cf9 --- /dev/null +++ b/.idea/.idea.MakeGenericAgain/.idea/encodings.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/.idea.MakeGenericAgain/.idea/indexLayout.xml b/.idea/.idea.MakeGenericAgain/.idea/indexLayout.xml new file mode 100644 index 0000000..7b08163 --- /dev/null +++ b/.idea/.idea.MakeGenericAgain/.idea/indexLayout.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/.idea.MakeGenericAgain/.idea/vcs.xml b/.idea/.idea.MakeGenericAgain/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/.idea.MakeGenericAgain/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/MakeGenericAgain/CommandLineArgs.cs b/MakeGenericAgain/CommandLineArgs.cs index 076288e..00d5c05 100644 --- a/MakeGenericAgain/CommandLineArgs.cs +++ b/MakeGenericAgain/CommandLineArgs.cs @@ -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(string[] args) + where T : new() + => Parse(string.Join(" ", args)); + + public static T Parse(string args) + where T : new() { - public static T Parse(string[] args) where T : new() - { - return Parse(string.Join(" ", args)); - } + T res = new(); + PropertyInfo[] props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); - public static T Parse(string args) where T : new() + foreach (PropertyInfo prop in props) { - var res = new T(); - var props = typeof(T).GetProperties(BindingFlags.Public | BindingFlags.Instance); + IEnumerable namesForProp = (prop.GetCustomAttribute()?.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()?.ParamNames.Select(s => s.EnsureStartsWith("-").ToLower()) ?? Enumerable.Empty()) - .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(); } \ No newline at end of file diff --git a/MakeGenericAgain/FromCommandLineAttribute.cs b/MakeGenericAgain/FromCommandLineAttribute.cs index 3584c2d..9d3bbf3 100644 --- a/MakeGenericAgain/FromCommandLineAttribute.cs +++ b/MakeGenericAgain/FromCommandLineAttribute.cs @@ -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; } } \ No newline at end of file diff --git a/MakeGenericAgain/GlobalUsings.cs b/MakeGenericAgain/GlobalUsings.cs new file mode 100644 index 0000000..ef3ba0c --- /dev/null +++ b/MakeGenericAgain/GlobalUsings.cs @@ -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; \ No newline at end of file diff --git a/MakeGenericAgain/MakeGenericAgain.csproj b/MakeGenericAgain/MakeGenericAgain.csproj index e71ed32..123ef2f 100644 --- a/MakeGenericAgain/MakeGenericAgain.csproj +++ b/MakeGenericAgain/MakeGenericAgain.csproj @@ -1,37 +1,37 @@  - - Exe - net6.0;net8.0 - latest - true - makeGenericAgain - ./nupkg - 1.0.5 - Florian Gilde - MakeGenericAgain - 0.0.0-dev - Git - true - - 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 - - - https://github.com/fgilde/MakeGenericAgain - Copyright © $(Authors) 2020-$([System.DateTime]::Now.Year) - MIT - README.md - + + Exe + net6.0;net8.0;net10.0 + latest + true + makeGenericAgain + ./nupkg + 1.0.7 + Florian Gilde + MakeGenericAgain + 0.0.0-dev + Git + true + + 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 + - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + https://github.com/fgilde/MakeGenericAgain + Copyright © $(Authors) 2020-$([System.DateTime]::Now.Year) + MIT + README.md + - - - + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + diff --git a/MakeGenericAgain/NameReplacer.cs b/MakeGenericAgain/NameReplacer.cs index fd0add0..fda91ea 100644 --- a/MakeGenericAgain/NameReplacer.cs +++ b/MakeGenericAgain/NameReplacer.cs @@ -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 typesToIgnore) { - public static string ReplaceToGeneric(string str, ICollection 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 + }; } } \ No newline at end of file diff --git a/MakeGenericAgain/Options.cs b/MakeGenericAgain/Options.cs index 7f2c77b..6fbb86d 100644 --- a/MakeGenericAgain/Options.cs +++ b/MakeGenericAgain/Options.cs @@ -1,16 +1,13 @@ -using System.Collections.Generic; +namespace MakeGenericAgain; -namespace MakeGenericAgain +public class Options { - public class Options - { - [FromCommandLine("f", nameof(FileName))] - public string FileName { get; set; } + [FromCommandLine("f", nameof(FileName))] + public string FileName { get; set; } - [FromCommandLine("o", nameof(OutputFileName))] - public string OutputFileName { get; set; } + [FromCommandLine("o", nameof(OutputFileName))] + public string OutputFileName { get; set; } - [FromCommandLine("i", nameof(TypesToIgnore))] - public ICollection TypesToIgnore { get; set; } = []; - } + [FromCommandLine("i", nameof(TypesToIgnore))] + public ICollection TypesToIgnore { get; set; } = []; } \ No newline at end of file diff --git a/MakeGenericAgain/Program.cs b/MakeGenericAgain/Program.cs index f85b404..ffd5573 100644 --- a/MakeGenericAgain/Program.cs +++ b/MakeGenericAgain/Program.cs @@ -1,55 +1,54 @@ -using System; -using System.IO; +namespace MakeGenericAgain; -namespace MakeGenericAgain +internal class Program { - class Program + private static Options _options; + + private static int Main(string[] args) { - private static Options options; - static int Main(string[] args) - { - options = CommandLineArgs.Parse(args); - return (int)Handle(); - } + _options = CommandLineArgs.Parse(args); + return (int)Handle(); + } - static ExitCode Handle() + private static ExitCode Handle() + { + try { - try - { - if (string.IsNullOrEmpty(options.FileName) || !File.Exists(options.FileName)) - { - return Return(ExitCode.InvalidFilename, $"Error Filename '{options.FileName}' is invalid or not existing"); - } - - var lines = File.ReadAllLines(options.FileName); - for (var index = 0; index < lines.Length; index++) - { - lines[index] = NameReplacer.ReplaceToGeneric(lines[index], options.TypesToIgnore); - } - File.WriteAllLines(options.OutputFileName ?? options.FileName, lines); - return Return(ExitCode.Success, $"Filename {options.FileName} has successfully been updated"); - } - catch (Exception e) - { - return Return(ExitCode.UnknownError, e.Message); - } - } + if (string.IsNullOrEmpty(_options.FileName) || !File.Exists(_options.FileName)) + return Return(ExitCode.InvalidFilename, $"Error Filename '{_options.FileName}' is invalid or not existing"); + + string[] lines = File.ReadAllLines(_options.FileName); + for (int index = 0; index < lines.Length; index++) + lines[index] = NameReplacer.ReplaceToGeneric(lines[index], _options.TypesToIgnore); + + File.WriteAllLines(_options.OutputFileName ?? _options.FileName, lines); - static ExitCode Return (ExitCode code, string message) + return Return(ExitCode.Success, $"Filename {_options.FileName} has successfully been updated"); + } + catch (Exception e) { - var color = Console.ForegroundColor; - Console.ForegroundColor = code == ExitCode.Success ? ConsoleColor.Green : ConsoleColor.Red; - Console.WriteLine(message); - Console.ForegroundColor = color; - return code; + return Return(ExitCode.UnknownError, e.Message); } - } - enum ExitCode : int + private static ExitCode Return(ExitCode code, string message) { - Success = 0, - InvalidFilename = 1, - UnknownError = 2 + ConsoleColor color = Console.ForegroundColor; + + Console.ForegroundColor = code == ExitCode.Success + ? ConsoleColor.Green + : ConsoleColor.Red; + + Console.WriteLine(message); + Console.ForegroundColor = color; + + return code; } } + +internal enum ExitCode +{ + Success = 0, + InvalidFilename = 1, + UnknownError = 2 +} \ No newline at end of file diff --git a/README.md b/README.md index 42f3f34..45255a2 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,12 @@ # MakeGenericAgain -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 + +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 More infos about the general problem can be found here - - https://github.com/RicoSuter/NSwag/issues/1139 - - https://blog.devgenius.io/nswag-csharp-client-with-generics-support-6ad6a09f81d6 +- https://github.com/RicoSuter/NSwag/issues/1139 +- https://blog.devgenius.io/nswag-csharp-client-with-generics-support-6ad6a09f81d6 To install it on other projects, add this to the csproj: @@ -15,7 +17,8 @@ To install it on other projects, add this to the csproj: ``` -You can optionally provide names of types to ignore (should include any type names containing the word 'Of' as a minimum) +You can optionally provide names of types to ignore (should include any type names containing the word 'Of' as a +minimum) ``` @@ -25,6 +28,7 @@ You can optionally provide names of types to ignore (should include any type nam ``` To run int use + ``` makeGenericAgain -f "C:\Path\client.cs" ``` @@ -36,13 +40,14 @@ Or with ignorable type names ``` Also you can specify specific output file with -o + ``` makeGenericAgain -f "C:\Path\client.cs" -i "IgnorableOfType,AnotherOfIgnorable" -o "C:\Path\client_with_generics.cs" ``` - - ## Links -[Github Repository](https://github.com/fgilde/MakeGenericAgain) | + +[Github Repository](https://github.com/fgilde/MakeGenericAgain) | [Nuget Package](https://www.nuget.org/packages/MakeGenericAgain/) -# \ No newline at end of file + +# diff --git a/version.json b/version.json index 74dd9db..78da150 100644 --- a/version.json +++ b/version.json @@ -1,17 +1,17 @@ { - "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", - "version": "1.0.6", - "cloudBuild": { - "buildNumber": { - "enabled": true, - "setVersionVariables": true, - "includeCommitId": { - "when": "nonPublicReleaseOnly", - "where": "buildMetadata" - } - } - }, - "publicReleaseRefSpec": [ - "^refs/heads/main$" - ] + "$schema": "https://raw.githubusercontent.com/dotnet/Nerdbank.GitVersioning/master/src/NerdBank.GitVersioning/version.schema.json", + "version": "1.0.7", + "cloudBuild": { + "buildNumber": { + "enabled": true, + "setVersionVariables": true, + "includeCommitId": { + "when": "nonPublicReleaseOnly", + "where": "buildMetadata" + } + } + }, + "publicReleaseRefSpec": [ + "^refs/heads/main$" + ] }