From fb553c5c0d215ae5d518e52f80d730d7dd92ebdf Mon Sep 17 00:00:00 2001 From: Boshi Lian Date: Thu, 14 Oct 2021 06:55:19 -0700 Subject: [PATCH] refactor generator code (#727) * refactor generator code * remove unused files * dotnet fmt --- gen/KubernetesGenerator/ApiGenerator.cs | 65 + gen/KubernetesGenerator/ClassNameHelper.cs | 152 +++ gen/KubernetesGenerator/GeneralNameHelper.cs | 207 +++ gen/KubernetesGenerator/INustacheHelper.cs | 7 + .../KubernetesGenerator.csproj | 32 +- gen/KubernetesGenerator/MetaHelper.cs | 95 ++ gen/KubernetesGenerator/ModelExtGenerator.cs | 33 + gen/KubernetesGenerator/ModelGenerator.cs | 29 + gen/KubernetesGenerator/ParamHelper.cs | 83 ++ gen/KubernetesGenerator/PluralHelper.cs | 74 + gen/KubernetesGenerator/Program.cs | 1195 ++--------------- gen/KubernetesGenerator/StringHelpers.cs | 128 ++ gen/KubernetesGenerator/TypeHelper.cs | 344 +++++ gen/KubernetesGenerator/UtilHelper.cs | 52 + .../VersionConverterGenerator.cs | 52 + gen/KubernetesGenerator/WatchGenerator.cs | 26 + .../IKubernetes.Watch.cs.template | 0 .../{ => templates}/IKubernetes.cs.template | 0 .../Kubernetes.Watch.cs.template | 0 .../{ => templates}/Kubernetes.cs.template | 0 .../KubernetesExtensions.cs.template | 0 .../{ => templates}/Model.cs.template | 0 .../ModelExtensions.cs.template | 0 .../ModelOperators.cs.template | 0 .../VersionConverter.cs.template | 0 25 files changed, 1467 insertions(+), 1107 deletions(-) create mode 100644 gen/KubernetesGenerator/ApiGenerator.cs create mode 100644 gen/KubernetesGenerator/ClassNameHelper.cs create mode 100644 gen/KubernetesGenerator/GeneralNameHelper.cs create mode 100644 gen/KubernetesGenerator/INustacheHelper.cs create mode 100644 gen/KubernetesGenerator/MetaHelper.cs create mode 100644 gen/KubernetesGenerator/ModelExtGenerator.cs create mode 100644 gen/KubernetesGenerator/ModelGenerator.cs create mode 100644 gen/KubernetesGenerator/ParamHelper.cs create mode 100644 gen/KubernetesGenerator/PluralHelper.cs create mode 100644 gen/KubernetesGenerator/StringHelpers.cs create mode 100644 gen/KubernetesGenerator/TypeHelper.cs create mode 100644 gen/KubernetesGenerator/UtilHelper.cs create mode 100644 gen/KubernetesGenerator/VersionConverterGenerator.cs create mode 100644 gen/KubernetesGenerator/WatchGenerator.cs rename gen/KubernetesGenerator/{ => templates}/IKubernetes.Watch.cs.template (100%) rename gen/KubernetesGenerator/{ => templates}/IKubernetes.cs.template (100%) rename gen/KubernetesGenerator/{ => templates}/Kubernetes.Watch.cs.template (100%) rename gen/KubernetesGenerator/{ => templates}/Kubernetes.cs.template (100%) rename gen/KubernetesGenerator/{ => templates}/KubernetesExtensions.cs.template (100%) rename gen/KubernetesGenerator/{ => templates}/Model.cs.template (100%) rename gen/KubernetesGenerator/{ => templates}/ModelExtensions.cs.template (100%) rename gen/KubernetesGenerator/{ => templates}/ModelOperators.cs.template (100%) rename gen/KubernetesGenerator/{ => templates}/VersionConverter.cs.template (100%) diff --git a/gen/KubernetesGenerator/ApiGenerator.cs b/gen/KubernetesGenerator/ApiGenerator.cs new file mode 100644 index 0000000..c89b79b --- /dev/null +++ b/gen/KubernetesGenerator/ApiGenerator.cs @@ -0,0 +1,65 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class ApiGenerator + { + public void Generate(SwaggerDocument swagger, string outputDirectory) + { + var data = swagger.Operations + .Where(o => o.Method != SwaggerOperationMethod.Options) + .GroupBy(o => o.Operation.OperationId) + .Select(g => + { + var gs = g.ToArray(); + + for (var i = 1; i < g.Count(); i++) + { + gs[i].Operation.OperationId += i; + } + + return gs; + }) + .SelectMany(g => g) + .Select(o => + { + var ps = o.Operation.ActualParameters.OrderBy(p => !p.IsRequired).ToArray(); + + o.Operation.Parameters.Clear(); + + var name = new HashSet(); + + var i = 1; + foreach (var p in ps) + { + if (name.Contains(p.Name)) + { + p.Name = p.Name + i++; + } + + o.Operation.Parameters.Add(p); + name.Add(p.Name); + } + + return o; + }) + .Select(o => + { + o.Path = o.Path.TrimStart('/'); + return o; + }) + .ToArray(); + + Render.FileToFile(Path.Combine("templates", "IKubernetes.cs.template"), data, + Path.Combine(outputDirectory, "IKubernetes.cs")); + Render.FileToFile(Path.Combine("templates", "Kubernetes.cs.template"), data, + Path.Combine(outputDirectory, "Kubernetes.cs")); + Render.FileToFile(Path.Combine("templates", "KubernetesExtensions.cs.template"), data, + Path.Combine(outputDirectory, "KubernetesExtensions.cs")); + } + } +} diff --git a/gen/KubernetesGenerator/ClassNameHelper.cs b/gen/KubernetesGenerator/ClassNameHelper.cs new file mode 100644 index 0000000..548f073 --- /dev/null +++ b/gen/KubernetesGenerator/ClassNameHelper.cs @@ -0,0 +1,152 @@ +using System.Collections.Generic; +using System.Linq; +using CaseExtensions; +using NJsonSchema; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class ClassNameHelper : INustacheHelper + { + private readonly Dictionary classNameMap; + private readonly HashSet schemaDefinitionsInMultipleGroups; + private readonly Dictionary schemaToNameMapCooked; + private readonly Dictionary schemaToNameMapUnprocessed; + + public ClassNameHelper(SwaggerDocument swaggerCooked, SwaggerDocument swaggerUnprocessed) + { + classNameMap = InitClassNameMap(swaggerCooked); + + schemaToNameMapCooked = GenerateSchemaToNameMapCooked(swaggerCooked); + schemaToNameMapUnprocessed = GenerateSchemaToNameMapUnprocessed(swaggerUnprocessed); + schemaDefinitionsInMultipleGroups = InitSchemaDefinitionsInMultipleGroups(schemaToNameMapUnprocessed); + } + + public void RegisterHelper() + { + Helpers.Register(nameof(GetClassName), GetClassName); + } + + private static Dictionary GenerateSchemaToNameMapUnprocessed( + SwaggerDocument swaggerUnprocessed) + { + return swaggerUnprocessed.Definitions.ToDictionary(x => x.Value, x => x.Key); + } + + private static Dictionary GenerateSchemaToNameMapCooked(SwaggerDocument swaggerCooked) + { + return swaggerCooked.Definitions.ToDictionary(x => x.Value, x => x.Key.Replace(".", "").ToPascalCase()); + } + + private static HashSet InitSchemaDefinitionsInMultipleGroups( + Dictionary schemaToNameMap) + { + return schemaToNameMap.Values.Select(x => + { + var parts = x.Split("."); + return new + { + FullName = x, + Name = parts[parts.Length - 1], + Version = parts[parts.Length - 2], + Group = parts[parts.Length - 3], + }; + }) + .GroupBy(x => new { x.Name, x.Version }) + .Where(x => x.Count() > 1) + .SelectMany(x => x) + .Select(x => x.FullName) + .ToHashSet(); + } + + private Dictionary InitClassNameMap(SwaggerDocument doc) + { + var map = new Dictionary(); + foreach (var (k, v) in doc.Definitions) + { + if (v.ExtensionData?.TryGetValue("x-kubernetes-group-version-kind", out _) == true) + { + var groupVersionKindElements = (object[])v.ExtensionData["x-kubernetes-group-version-kind"]; + var groupVersionKind = (Dictionary)groupVersionKindElements[0]; + + var group = (string)groupVersionKind["group"]; + var kind = (string)groupVersionKind["kind"]; + var version = (string)groupVersionKind["version"]; + map[$"{group}_{kind}_{version}"] = k.Replace(".", "").ToPascalCase(); + } + } + + return map; + } + + public void GetClassName(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerOperation) + { + context.Write(GetClassName(arguments[0] as SwaggerOperation)); + } + else if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) + { + context.Write(GetClassNameForSchemaDefinition(arguments[0] as JsonSchema4)); + } + } + + public string GetClassName(SwaggerOperation operation) + { + var groupVersionKind = + (Dictionary)operation.ExtensionData["x-kubernetes-group-version-kind"]; + return GetClassName(groupVersionKind); + } + + public string GetClassName(Dictionary groupVersionKind) + { + var group = (string)groupVersionKind["group"]; + var kind = (string)groupVersionKind["kind"]; + var version = (string)groupVersionKind["version"]; + + return classNameMap[$"{group}_{kind}_{version}"]; + } + + public string GetClassName(JsonSchema4 definition) + { + var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; + var groupVersionKind = (Dictionary)groupVersionKindElements[0]; + + return GetClassName(groupVersionKind); + } + + public string GetClassNameForSchemaDefinition(JsonSchema4 definition) + { + if (definition.ExtensionData != null && + definition.ExtensionData.ContainsKey("x-kubernetes-group-version-kind")) + { + return GetClassName(definition); + } + + if (schemaToNameMapCooked.TryGetValue(definition, out var name)) + { + return name; + } + + var schemaName = schemaToNameMapUnprocessed[definition]; + + var parts = schemaName.Split("."); + var group = parts[parts.Length - 3]; + var version = parts[parts.Length - 2]; + var entityName = parts[parts.Length - 1]; + if (!schemaDefinitionsInMultipleGroups.Contains(schemaName)) + { + group = null; + } + + return $"{group}{version}{entityName}".ToPascalCase(); + } + + private static Dictionary InitSchemaToNameCooked(SwaggerDocument swaggercooked) + { + return swaggercooked.Definitions.ToDictionary(x => x.Value, x => x.Key.Replace(".", "").ToPascalCase()); + } + } +} diff --git a/gen/KubernetesGenerator/GeneralNameHelper.cs b/gen/KubernetesGenerator/GeneralNameHelper.cs new file mode 100644 index 0000000..f96fd18 --- /dev/null +++ b/gen/KubernetesGenerator/GeneralNameHelper.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using CaseExtensions; +using NJsonSchema; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class GeneralNameHelper : INustacheHelper + { + private readonly ClassNameHelper classNameHelper; + + public GeneralNameHelper(ClassNameHelper classNameHelper) + { + this.classNameHelper = classNameHelper; + } + + public void RegisterHelper() + { + Helpers.Register(nameof(GetInterfaceName), GetInterfaceName); + Helpers.Register(nameof(GetMethodName), GetMethodName); + Helpers.Register(nameof(GetDotNetName), GetDotNetName); + } + + public void GetInterfaceName(RenderContext context, IList arguments, + IDictionary options, RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) + { + context.Write(GetInterfaceName(arguments[0] as JsonSchema4)); + } + } + + private string GetInterfaceName(JsonSchema4 definition) + { + var interfaces = new List(); + if (definition.Properties.TryGetValue("metadata", out var metadataProperty)) + { + interfaces.Add( + $"IKubernetesObject<{classNameHelper.GetClassNameForSchemaDefinition(metadataProperty.Reference)}>"); + } + else + { + interfaces.Add("IKubernetesObject"); + } + + if (definition.Properties.TryGetValue("items", out var itemsProperty)) + { + var schema = itemsProperty.Type == JsonObjectType.Object + ? itemsProperty.Reference + : itemsProperty.Item.Reference; + interfaces.Add($"IItems<{classNameHelper.GetClassNameForSchemaDefinition(schema)}>"); + } + + if (definition.Properties.TryGetValue("spec", out var specProperty)) + { + // ignore empty spec placeholder + if (specProperty.Reference.ActualProperties.Any()) + { + interfaces.Add($"ISpec<{classNameHelper.GetClassNameForSchemaDefinition(specProperty.Reference)}>"); + } + } + + interfaces.Add("IValidate"); + + return string.Join(", ", interfaces); + } + + public void GetMethodName(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerOperation) + { + string suffix = null; + if (arguments.Count > 1) + { + suffix = arguments[1] as string; + } + + context.Write(GetMethodName(arguments[0] as SwaggerOperation, suffix)); + } + } + + public void GetDotNetName(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerParameter) + { + var parameter = arguments[0] as SwaggerParameter; + context.Write(GetDotNetName(parameter.Name)); + + if (arguments.Count > 1 && arguments[1] as string == "true" && !parameter.IsRequired) + { + context.Write(" = null"); + } + } + else if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is string) + { + var style = "parameter"; + if (arguments.Count > 1) + { + style = arguments[1] as string; + } + + context.Write(GetDotNetName((string)arguments[0], style)); + } + } + + public string GetDotNetName(string jsonName, string style = "parameter") + { + switch (style) + { + case "parameter": + if (jsonName == "namespace") + { + return "namespaceParameter"; + } + else if (jsonName == "continue") + { + return "continueParameter"; + } + + break; + + case "fieldctor": + if (jsonName == "namespace") + { + return "namespaceProperty"; + } + else if (jsonName == "continue") + { + return "continueProperty"; + } + else if (jsonName == "__referencePath") + { + return "refProperty"; + } + else if (jsonName == "default") + { + return "defaultProperty"; + } + else if (jsonName == "operator") + { + return "operatorProperty"; + } + else if (jsonName == "$schema") + { + return "schema"; + } + else if (jsonName == "enum") + { + return "enumProperty"; + } + else if (jsonName == "object") + { + return "objectProperty"; + } + else if (jsonName == "readOnly") + { + return "readOnlyProperty"; + } + else if (jsonName == "from") + { + return "fromProperty"; + } + + if (jsonName.Contains("-")) + { + return jsonName.ToCamelCase(); + } + + break; + case "field": + return GetDotNetName(jsonName, "fieldctor").ToPascalCase(); + } + + return jsonName.ToCamelCase(); + } + + public static string GetMethodName(SwaggerOperation watchOperation, string suffix) + { + var tag = watchOperation.Tags[0]; + tag = tag.Replace("_", string.Empty); + + var methodName = watchOperation.OperationId.ToPascalCase(); + + switch (suffix) + { + case "": + case "Async": + case "WithHttpMessagesAsync": + methodName += suffix; + break; + + default: + // This tries to remove the version from the method name, e.g. watchCoreV1NamespacedPod => WatchNamespacedPod + methodName = methodName.Replace(tag, string.Empty, StringComparison.OrdinalIgnoreCase); + methodName += "Async"; + break; + } + + return methodName; + } + } +} diff --git a/gen/KubernetesGenerator/INustacheHelper.cs b/gen/KubernetesGenerator/INustacheHelper.cs new file mode 100644 index 0000000..25c57f0 --- /dev/null +++ b/gen/KubernetesGenerator/INustacheHelper.cs @@ -0,0 +1,7 @@ +namespace KubernetesGenerator +{ + public interface INustacheHelper + { + void RegisterHelper(); + } +} diff --git a/gen/KubernetesGenerator/KubernetesGenerator.csproj b/gen/KubernetesGenerator/KubernetesGenerator.csproj index 6bb6aca..24e6476 100644 --- a/gen/KubernetesGenerator/KubernetesGenerator.csproj +++ b/gen/KubernetesGenerator/KubernetesGenerator.csproj @@ -1,20 +1,22 @@ - + - - Exe - net5 - + + Exe + net5 + - - - - - + + + + + + + - - - PreserveNewest - - + + + PreserveNewest + + diff --git a/gen/KubernetesGenerator/MetaHelper.cs b/gen/KubernetesGenerator/MetaHelper.cs new file mode 100644 index 0000000..ac6ab58 --- /dev/null +++ b/gen/KubernetesGenerator/MetaHelper.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; +using NJsonSchema; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class MetaHelper : INustacheHelper + { + public void RegisterHelper() + { + Helpers.Register(nameof(GetGroup), GetGroup); + Helpers.Register(nameof(GetApiVersion), GetApiVersion); + Helpers.Register(nameof(GetKind), GetKind); + Helpers.Register(nameof(GetPathExpression), GetPathExpression); + } + + public static void GetKind(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) + { + context.Write(GetKind(arguments[0] as JsonSchema4)); + } + } + + private static string GetKind(JsonSchema4 definition) + { + var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; + var groupVersionKind = (Dictionary)groupVersionKindElements[0]; + + return groupVersionKind["kind"] as string; + } + + public static void GetGroup(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) + { + context.Write(GetGroup(arguments[0] as JsonSchema4)); + } + } + + private static string GetGroup(JsonSchema4 definition) + { + var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; + var groupVersionKind = (Dictionary)groupVersionKindElements[0]; + + return groupVersionKind["group"] as string; + } + + public static void GetApiVersion(RenderContext context, IList arguments, + IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) + { + context.Write(GetApiVersion(arguments[0] as JsonSchema4)); + } + } + + private static string GetApiVersion(JsonSchema4 definition) + { + var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; + var groupVersionKind = (Dictionary)groupVersionKindElements[0]; + + return groupVersionKind["version"] as string; + } + + public static void GetPathExpression(RenderContext context, IList arguments, + IDictionary options, RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && + arguments[0] is SwaggerOperationDescription) + { + var operation = arguments[0] as SwaggerOperationDescription; + context.Write(GetPathExpression(operation)); + } + } + + private static string GetPathExpression(SwaggerOperationDescription operation) + { + var pathExpression = operation.Path; + + if (pathExpression.StartsWith("/", StringComparison.InvariantCulture)) + { + pathExpression = pathExpression.Substring(1); + } + + pathExpression = pathExpression.Replace("{namespace}", "{namespaceParameter}"); + return pathExpression; + } + } +} diff --git a/gen/KubernetesGenerator/ModelExtGenerator.cs b/gen/KubernetesGenerator/ModelExtGenerator.cs new file mode 100644 index 0000000..e4967b2 --- /dev/null +++ b/gen/KubernetesGenerator/ModelExtGenerator.cs @@ -0,0 +1,33 @@ +using System.Collections.Generic; +using System.IO; +using System.Linq; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class ModelExtGenerator + { + private readonly ClassNameHelper classNameHelper; + + public ModelExtGenerator(ClassNameHelper classNameHelper) + { + this.classNameHelper = classNameHelper; + } + + public void Generate(SwaggerDocument swagger, string outputDirectory) + { + // Generate the interface declarations + var skippedTypes = new HashSet { "V1WatchEvent" }; + + var definitions = swagger.Definitions.Values + .Where( + d => d.ExtensionData != null + && d.ExtensionData.ContainsKey("x-kubernetes-group-version-kind") + && !skippedTypes.Contains(classNameHelper.GetClassName(d))); + + Render.FileToFile(Path.Combine("templates", "ModelExtensions.cs.template"), definitions, + Path.Combine(outputDirectory, "ModelExtensions.cs")); + } + } +} diff --git a/gen/KubernetesGenerator/ModelGenerator.cs b/gen/KubernetesGenerator/ModelGenerator.cs new file mode 100644 index 0000000..01570fc --- /dev/null +++ b/gen/KubernetesGenerator/ModelGenerator.cs @@ -0,0 +1,29 @@ +using System.IO; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class ModelGenerator + { + private readonly ClassNameHelper classNameHelper; + + public ModelGenerator(ClassNameHelper classNameHelper) + { + this.classNameHelper = classNameHelper; + } + + public void Generate(SwaggerDocument swaggercooked, string outputDirectory) + { + Directory.CreateDirectory(Path.Combine(outputDirectory, "Models")); + + foreach (var (_, def) in swaggercooked.Definitions) + { + var clz = classNameHelper.GetClassNameForSchemaDefinition(def); + Render.FileToFile(Path.Combine("templates", "Model.cs.template"), + new { clz, def, properties = def.Properties.Values }, + Path.Combine(outputDirectory, "Models", $"{clz}.cs")); + } + } + } +} diff --git a/gen/KubernetesGenerator/ParamHelper.cs b/gen/KubernetesGenerator/ParamHelper.cs new file mode 100644 index 0000000..5007769 --- /dev/null +++ b/gen/KubernetesGenerator/ParamHelper.cs @@ -0,0 +1,83 @@ +using System.Collections.Generic; +using System.Linq; +using NJsonSchema; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class ParamHelper : INustacheHelper + { + private readonly GeneralNameHelper generalNameHelper; + private readonly TypeHelper typeHelper; + + public ParamHelper(GeneralNameHelper generalNameHelper, TypeHelper typeHelper) + { + this.generalNameHelper = generalNameHelper; + this.typeHelper = typeHelper; + } + + public void RegisterHelper() + { + Helpers.Register(nameof(IfParamCotains), IfParamCotains); + Helpers.Register(nameof(GetModelCtorParam), GetModelCtorParam); + } + + public static void IfParamCotains(RenderContext context, IList arguments, + IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + var operation = arguments?.FirstOrDefault() as SwaggerOperation; + if (operation != null) + { + string name = null; + if (arguments.Count > 1) + { + name = arguments[1] as string; + } + + var found = false; + + foreach (var param in operation.Parameters) + { + if (param.Name == name) + { + found = true; + break; + } + } + + if (found) + { + fn(null); + } + } + } + + + public void GetModelCtorParam(RenderContext context, IList arguments, + IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + var schema = arguments[0] as JsonSchema4; + + if (schema != null) + { + context.Write(string.Join(", ", schema.Properties.Values + .OrderBy(p => !p.IsRequired) + .Select(p => + { + var sp = + $"{typeHelper.GetDotNetType(p)} {generalNameHelper.GetDotNetName(p.Name, "fieldctor")}"; + + if (!p.IsRequired) + { + sp = $"{sp} = null"; + } + + return sp; + }))); + } + } + } +} diff --git a/gen/KubernetesGenerator/PluralHelper.cs b/gen/KubernetesGenerator/PluralHelper.cs new file mode 100644 index 0000000..216894b --- /dev/null +++ b/gen/KubernetesGenerator/PluralHelper.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NJsonSchema; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class PluralHelper : INustacheHelper + { + private readonly Dictionary _classNameToPluralMap; + private readonly ClassNameHelper classNameHelper; + + public PluralHelper(ClassNameHelper classNameHelper, SwaggerDocument swagger) + { + this.classNameHelper = classNameHelper; + _classNameToPluralMap = InitClassNameToPluralMap(swagger); + } + + public void RegisterHelper() + { + Helpers.Register(nameof(GetPlural), GetPlural); + } + + public void GetPlural(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) + { + var plural = GetPlural(arguments[0] as JsonSchema4); + if (plural != null) + { + context.Write($"\"{plural}\""); + } + else + { + context.Write("null"); + } + } + } + + public string GetPlural(JsonSchema4 definition) + { + var className = classNameHelper.GetClassNameForSchemaDefinition(definition); + return _classNameToPluralMap.GetValueOrDefault(className, null); + } + + private Dictionary InitClassNameToPluralMap(SwaggerDocument swagger) + { + var classNameToPluralMap = swagger.Operations + .Where(x => x.Operation.OperationId.StartsWith("list", StringComparison.InvariantCulture)) + .Select(x => new + { + PluralName = x.Path.Split("/").Last(), + ClassName = classNameHelper.GetClassNameForSchemaDefinition(x.Operation.Responses["200"] + .ActualResponseSchema), + }) + .Distinct() + .ToDictionary(x => x.ClassName, x => x.PluralName); + + // dictionary only contains "list" plural maps. assign the same plural names to entities those lists support + classNameToPluralMap = classNameToPluralMap + .Where(x => x.Key.EndsWith("List", StringComparison.InvariantCulture)) + .Select(x => + new { ClassName = x.Key.Remove(x.Key.Length - 4), PluralName = x.Value }) + .ToDictionary(x => x.ClassName, x => x.PluralName) + .Union(classNameToPluralMap) + .ToDictionary(x => x.Key, x => x.Value); + + return classNameToPluralMap; + } + } +} diff --git a/gen/KubernetesGenerator/Program.cs b/gen/KubernetesGenerator/Program.cs index 3afa8ab..1e9f607 100644 --- a/gen/KubernetesGenerator/Program.cs +++ b/gen/KubernetesGenerator/Program.cs @@ -1,1120 +1,131 @@ -using CaseExtensions; -using NJsonSchema; -using NSwag; -using Nustache.Core; -using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Security; -using System.Text.RegularExpressions; using System.Threading.Tasks; +using Autofac; +using CommandLine; +using KubernetesGenerator; +using NSwag; namespace KubernetesWatchGenerator { internal class Program { - private static HashSet _classesWithValidation; - private static readonly Dictionary ClassNameMap = new Dictionary(); - private static Dictionary _schemaToNameMap; - private static Dictionary _schemaToNameMapCooked; - private static HashSet _schemaDefinitionsInMultipleGroups; - private static Dictionary _classNameToPluralMap; - private static async Task Main(string[] args) { - if (args.Length < 2) - { - Console.Error.WriteLine($"usage {args[0]} path/to/generated"); - Environment.Exit(1); - } - - var outputDirectory = args[1]; - - // Read the spec trimmed - // here we cache all name in gen project for later use - var swaggercooked = await SwaggerDocument.FromFileAsync(Path.Combine(args[1], "swagger.json")).ConfigureAwait(false); - foreach (var (k, v) in swaggercooked.Definitions) - { - if (v.ExtensionData?.TryGetValue("x-kubernetes-group-version-kind", out var _) == true) - { - var groupVersionKindElements = (object[])v.ExtensionData["x-kubernetes-group-version-kind"]; - var groupVersionKind = (Dictionary)groupVersionKindElements[0]; - - var group = (string)groupVersionKind["group"]; - var kind = (string)groupVersionKind["kind"]; - var version = (string)groupVersionKind["version"]; - ClassNameMap[$"{group}_{kind}_{version}"] = ToPascalCase(k.Replace(".", "")); - } - } - - _schemaToNameMapCooked = swaggercooked.Definitions.ToDictionary(x => x.Value, x => ToPascalCase(x.Key.Replace(".", ""))); - - // gen project removed all watch operations, so here we switch back to unprocessed version - var swagger = await SwaggerDocument.FromFileAsync(Path.Combine(args[1], "swagger.json.unprocessed")).ConfigureAwait(false); - _schemaToNameMap = swagger.Definitions.ToDictionary(x => x.Value, x => x.Key); - _schemaDefinitionsInMultipleGroups = _schemaToNameMap.Values.Select(x => - { - var parts = x.Split("."); - return new - { - FullName = x, - Name = parts[parts.Length - 1], - Version = parts[parts.Length - 2], - Group = parts[parts.Length - 3], - }; - }) - .GroupBy(x => new { x.Name, x.Version }) - .Where(x => x.Count() > 1) - .SelectMany(x => x) - .Select(x => x.FullName) - .ToHashSet(); - - _classNameToPluralMap = swagger.Operations - .Where(x => x.Operation.OperationId.StartsWith("list", StringComparison.InvariantCulture)) - .Select(x => - { - return new - { - PluralName = x.Path.Split("/").Last(), - ClassName = GetClassNameForSchemaDefinition(x.Operation.Responses["200"] - .ActualResponseSchema), - }; - }) - .Distinct() - .ToDictionary(x => x.ClassName, x => x.PluralName); - - // dictionary only contains "list" plural maps. assign the same plural names to entities those lists support - _classNameToPluralMap = _classNameToPluralMap - .Where(x => x.Key.EndsWith("List", StringComparison.InvariantCulture)) - .Select(x => - new { ClassName = x.Key.Remove(x.Key.Length - 4), PluralName = x.Value }) - .ToDictionary(x => x.ClassName, x => x.PluralName) - .Union(_classNameToPluralMap) - .ToDictionary(x => x.Key, x => x.Value); - - // Register helpers used in the templating. - Helpers.Register(nameof(ToXmlDoc), ToXmlDoc); - Helpers.Register(nameof(GetClassName), GetClassName); - Helpers.Register(nameof(GetInterfaceName), GetInterfaceName); - Helpers.Register(nameof(GetMethodName), GetMethodName); - Helpers.Register(nameof(GetDotNetName), GetDotNetName); - Helpers.Register(nameof(GetDotNetType), GetDotNetType); - Helpers.Register(nameof(GetPathExpression), GetPathExpression); - Helpers.Register(nameof(GetGroup), GetGroup); - Helpers.Register(nameof(GetApiVersion), GetApiVersion); - Helpers.Register(nameof(GetKind), GetKind); - Helpers.Register(nameof(GetPlural), GetPlural); - Helpers.Register(nameof(GetTuple), GetTuple); - Helpers.Register(nameof(GetReturnType), GetReturnType); - Helpers.Register(nameof(IfKindIs), IfKindIs); - Helpers.Register(nameof(AddCurly), AddCurly); - Helpers.Register(nameof(GetRequestMethod), GetRequestMethod); - Helpers.Register(nameof(EscapeDataString), EscapeDataString); - Helpers.Register(nameof(IfReturnType), IfReturnType); - Helpers.Register(nameof(IfParamCotains), IfParamCotains); - Helpers.Register(nameof(GetModelCtorParam), GetModelCtorParam); - Helpers.Register(nameof(IfType), IfType); - - // Generate the Watcher operations - // We skip operations where the name of the class in the C# client could not be determined correctly. - // That's usually because there are different version of the same object (e.g. for deployments). - var blacklistedOperations = new HashSet() { }; - - var watchOperations = swagger.Operations.Where( - o => o.Path.Contains("/watch/") - && o.Operation.ActualParameters.Any(p => p.Name == "name") - && !blacklistedOperations.Contains(o.Operation.OperationId)).ToArray(); - - // Render. - Render.FileToFile("IKubernetes.Watch.cs.template", watchOperations, - Path.Combine(outputDirectory, "IKubernetes.Watch.cs")); - Render.FileToFile("Kubernetes.Watch.cs.template", watchOperations, - Path.Combine(outputDirectory, "Kubernetes.Watch.cs")); - - var data = swaggercooked.Operations - .Where(o => o.Method != SwaggerOperationMethod.Options) - .GroupBy(o => o.Operation.OperationId) - .Select(g => - { - var gs = g.ToArray(); - - for (int i = 1; i < g.Count(); i++) - { - gs[i].Operation.OperationId += i; - } - - return gs; - }) - .SelectMany(g => g) - .Select(o => - { - var ps = o.Operation.ActualParameters.OrderBy(p => !p.IsRequired).ToArray(); - - o.Operation.Parameters.Clear(); - - var name = new HashSet(); - - var i = 1; - foreach (var p in ps) - { - if (name.Contains(p.Name)) - { - p.Name = p.Name + i++; - } - - o.Operation.Parameters.Add(p); - name.Add(p.Name); - } - - return o; - }) - .Select(o => - { - o.Path = o.Path.TrimStart('/'); - return o; - }) - .ToArray(); - - Render.FileToFile("IKubernetes.cs.template", data, Path.Combine(outputDirectory, "IKubernetes.cs")); - Render.FileToFile("KubernetesExtensions.cs.template", data, Path.Combine(outputDirectory, "KubernetesExtensions.cs")); - Render.FileToFile("Kubernetes.cs.template", data, Path.Combine(outputDirectory, "Kubernetes.cs")); - - Directory.CreateDirectory(Path.Combine(outputDirectory, "Models")); - - foreach (var (_, def) in swaggercooked.Definitions) - { - var clz = GetClassNameForSchemaDefinition(def); - Render.FileToFile("Model.cs.template", new - { - clz, - def, - properties = def.Properties.Values, - }, Path.Combine(outputDirectory, "Models", $"{clz}.cs")); - } - - // Generate the interface declarations - var skippedTypes = new HashSet() { "V1WatchEvent", }; - - var definitions = swagger.Definitions.Values - .Where( - d => d.ExtensionData != null - && d.ExtensionData.ContainsKey("x-kubernetes-group-version-kind") - && !skippedTypes.Contains(GetClassName(d))); - - var modelsDir = Path.Combine(outputDirectory, "Models"); - _classesWithValidation = Directory.EnumerateFiles(modelsDir) - .Select(x => new { Class = Path.GetFileNameWithoutExtension(x), Content = File.ReadAllText(x) }) - .Where(x => x.Content.Contains("public virtual void Validate()")) - .Select(x => x.Class) - .ToHashSet(); - - Render.FileToFile("ModelExtensions.cs.template", definitions, Path.Combine(outputDirectory, "ModelExtensions.cs")); - - // generate version converter maps - var allGeneratedModelClassNames = Directory - .EnumerateFiles(Path.Combine(outputDirectory, "Models")) - .Select(Path.GetFileNameWithoutExtension) - .ToList(); - - var versionRegex = @"(^V|v)[0-9]+((alpha|beta)[0-9]+)?"; - var typePairs = allGeneratedModelClassNames - .OrderBy(x => x) - .Select(x => new { Version = Regex.Match(x, versionRegex).Value?.ToLower(), Kinda = Regex.Replace(x, versionRegex, string.Empty), Type = x }) - .Where(x => !string.IsNullOrEmpty(x.Version)) - .GroupBy(x => x.Kinda) - .Where(x => x.Count() > 1) - .SelectMany(x => x.SelectMany((value, index) => x.Skip(index + 1), (first, second) => new { first, second })) - .OrderBy(x => x.first.Kinda) - .ThenBy(x => x.first.Version) - .Select(x => (ITuple)Tuple.Create(x.first.Type, x.second.Type)) - .ToList(); - - var versionFile = File.ReadAllText(Path.Combine(outputDirectory, "..", "Versioning", "VersionConverter.cs")); - var manualMaps = Regex.Matches(versionFile, @"\.CreateMap<(?.+?),\s?(?.+?)>") - .Select(x => Tuple.Create(x.Groups["T1"].Value, x.Groups["T2"].Value)) - .ToList(); - var versionConverterPairs = typePairs.Except(manualMaps).ToList(); - - Render.FileToFile("VersionConverter.cs.template", versionConverterPairs, Path.Combine(outputDirectory, "VersionConverter.cs")); - Render.FileToFile("ModelOperators.cs.template", typePairs, Path.Combine(outputDirectory, "ModelOperators.cs")); + await Parser.Default.ParseArguments(args) + .WithParsedAsync(RunAsync).ConfigureAwait(false); } - private static void ToXmlDoc(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) + private static async Task RunAsync(Options options) { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is string) + var outputDirectory = options.OutputPath; + + var swaggerCooked = await SwaggerDocument.FromFileAsync(Path.Combine(outputDirectory, "swagger.json")) + .ConfigureAwait(false); + var swaggerUnprocessed = await SwaggerDocument + .FromFileAsync(Path.Combine(outputDirectory, "swagger.json.unprocessed")) + .ConfigureAwait(false); + + + var builder = new ContainerBuilder(); + + builder.RegisterType() + .WithParameter(new NamedParameter(nameof(swaggerCooked), swaggerCooked)) + .WithParameter(new NamedParameter(nameof(swaggerUnprocessed), swaggerUnprocessed)) + .AsSelf() + .AsImplementedInterfaces() + ; + + builder.RegisterType() + .AsImplementedInterfaces() + ; + + builder.RegisterType() + .AsImplementedInterfaces() + ; + + builder.RegisterType() + .WithParameter(new TypedParameter(typeof(SwaggerDocument), swaggerUnprocessed)) + .AsImplementedInterfaces() + ; + + builder.RegisterType() + .AsSelf() + .AsImplementedInterfaces() + ; + + builder.RegisterType() + .AsSelf() + .AsImplementedInterfaces() + ; + + builder.RegisterType() + .AsImplementedInterfaces() + ; + + builder.RegisterType() + .AsImplementedInterfaces() + ; + + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + builder.RegisterType(); + + var container = builder.Build(); + + foreach (var helper in container.Resolve>()) { - var first = true; + helper.RegisterHelper(); + } - using (var reader = new StringReader(arguments[0] as string)) - { - string line = null; - while ((line = reader.ReadLine()) != null) - { - foreach (var wline in WordWrap(line, 80)) - { - if (!first) - { - context.Write("\n"); - context.Write(" /// "); - } - else - { - first = false; - } + if (options.GenerateWatch) + { + container.Resolve().Generate(swaggerUnprocessed, outputDirectory); + } - context.Write(SecurityElement.Escape(wline)); - } - } - } + if (options.GenerateApi) + { + container.Resolve().Generate(swaggerCooked, outputDirectory); + } + + if (options.GenerateModel) + { + container.Resolve().Generate(swaggerCooked, outputDirectory); + } + + if (options.GenerateModelExt) + { + container.Resolve().Generate(swaggerUnprocessed, outputDirectory); + } + + if (options.GenerateVersionConverter) + { + container.Resolve().GenerateFromModels(outputDirectory); } } - private static void GetTuple(RenderContext context, IList arguments, IDictionary options, RenderBlock fn, RenderBlock inverse) + [System.Diagnostics.CodeAnalysis.SuppressMessage("Usage", "CA1812", Justification = "Instanced in CommandLineParser")] + public class Options { - if (arguments != null && arguments.Count > 0 && arguments[0] is ITuple && options.TryGetValue("index", out var indexObj) && int.TryParse(indexObj?.ToString(), out var index)) - { - var pair = (ITuple)arguments[0]; - var value = pair[index]; - context.Write(value.ToString()); - } - } + [Value(0, Required = true, HelpText = "path to src/KubernetesClient/generated")] + public string OutputPath { get; set; } - private static void GetClassName(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerOperation) - { - context.Write(GetClassName(arguments[0] as SwaggerOperation)); - } - else if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) - { - context.Write(GetClassNameForSchemaDefinition(arguments[0] as JsonSchema4)); - } - } + [Option("watch", Required = false, Default = true)] + public bool GenerateWatch { get; set; } - private static string GetClassName(SwaggerOperation watchOperation) - { - var groupVersionKind = - (Dictionary)watchOperation.ExtensionData["x-kubernetes-group-version-kind"]; - return GetClassName(groupVersionKind); - } + [Option("api", Required = false, Default = true)] + public bool GenerateApi { get; set; } - private static string GetClassName(Dictionary groupVersionKind) - { - var group = (string)groupVersionKind["group"]; - var kind = (string)groupVersionKind["kind"]; - var version = (string)groupVersionKind["version"]; + [Option("model", Required = false, Default = true)] + public bool GenerateModel { get; set; } - return ClassNameMap[$"{group}_{kind}_{version}"]; - } + [Option("modelext", Required = false, Default = true)] + public bool GenerateModelExt { get; set; } - private static string GetClassName(JsonSchema4 definition) - { - var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; - var groupVersionKind = (Dictionary)groupVersionKindElements[0]; - - return GetClassName(groupVersionKind); - } - - private static void GetInterfaceName(RenderContext context, IList arguments, - IDictionary options, RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) - { - context.Write(GetInterfaceName(arguments[0] as JsonSchema4)); - } - } - - private static string GetClassNameForSchemaDefinition(JsonSchema4 definition) - { - if (definition.ExtensionData != null && - definition.ExtensionData.ContainsKey("x-kubernetes-group-version-kind")) - { - return GetClassName(definition); - } - - if (_schemaToNameMapCooked.TryGetValue(definition, out var name)) - { - return name; - } - - var schemaName = _schemaToNameMap[definition]; - - var parts = schemaName.Split("."); - var group = parts[parts.Length - 3]; - var version = parts[parts.Length - 2]; - var entityName = parts[parts.Length - 1]; - if (!_schemaDefinitionsInMultipleGroups.Contains(schemaName)) - { - @group = null; - } - - var className = ToPascalCase($"{group}{version}{entityName}"); - return className; - } - - private static string GetInterfaceName(JsonSchema4 definition) - { - var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; - var groupVersionKind = (Dictionary)groupVersionKindElements[0]; - - var group = groupVersionKind["group"] as string; - var version = groupVersionKind["version"] as string; - var kind = groupVersionKind["kind"] as string; - var className = GetClassName(definition); - var interfaces = new List(); - if (definition.Properties.TryGetValue("metadata", out var metadataProperty)) - { - interfaces.Add($"IKubernetesObject<{GetClassNameForSchemaDefinition(metadataProperty.Reference)}>"); - } - else - { - interfaces.Add("IKubernetesObject"); - } - - if (definition.Properties.TryGetValue("items", out var itemsProperty)) - { - var schema = itemsProperty.Type == JsonObjectType.Object - ? itemsProperty.Reference - : itemsProperty.Item.Reference; - interfaces.Add($"IItems<{GetClassNameForSchemaDefinition(schema)}>"); - } - - if (definition.Properties.TryGetValue("spec", out var specProperty)) - { - // ignore empty spec placeholder - if (specProperty.Reference.ActualProperties.Any()) - { - interfaces.Add($"ISpec<{GetClassNameForSchemaDefinition(specProperty.Reference)}>"); - } - } - - if (_classesWithValidation.Contains(className)) - { - interfaces.Add("IValidate"); - } - - var result = string.Join(", ", interfaces); - return result; - } - - private static void GetKind(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) - { - context.Write(GetKind(arguments[0] as JsonSchema4)); - } - } - - private static string GetKind(JsonSchema4 definition) - { - var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; - var groupVersionKind = (Dictionary)groupVersionKindElements[0]; - - return groupVersionKind["kind"] as string; - } - - private static void GetPlural(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) - { - var plural = GetPlural(arguments[0] as JsonSchema4); - if (plural != null) - { - context.Write($"\"{plural}\""); - } - else - { - context.Write("null"); - } - } - } - - private static string GetPlural(JsonSchema4 definition) - { - var className = GetClassNameForSchemaDefinition(definition); - return _classNameToPluralMap.GetValueOrDefault(className, null); - } - - private static void GetGroup(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) - { - context.Write(GetGroup(arguments[0] as JsonSchema4)); - } - } - - private static string GetGroup(JsonSchema4 definition) - { - var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; - var groupVersionKind = (Dictionary)groupVersionKindElements[0]; - - return groupVersionKind["group"] as string; - } - - private static void GetMethodName(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerOperation) - { - string suffix = null; - if (arguments.Count > 1) - { - suffix = arguments[1] as string; - } - - context.Write(GetMethodName(arguments[0] as SwaggerOperation, suffix)); - } - } - - private static string GetMethodName(SwaggerOperation watchOperation, string suffix) - { - var tag = watchOperation.Tags[0]; - tag = tag.Replace("_", string.Empty); - - var methodName = ToPascalCase(watchOperation.OperationId); - - switch (suffix) - { - case "": - case "Async": - case "WithHttpMessagesAsync": - methodName += suffix; - break; - - default: - // This tries to remove the version from the method name, e.g. watchCoreV1NamespacedPod => WatchNamespacedPod - methodName = methodName.Replace(tag, string.Empty, StringComparison.OrdinalIgnoreCase); - methodName += "Async"; - break; - } - - return methodName; - } - - private static void GetDotNetType(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerParameter) - { - var parameter = arguments[0] as SwaggerParameter; - - if (parameter.Schema?.Reference != null) - { - context.Write(GetClassNameForSchemaDefinition(parameter.Schema.Reference)); - } - else if (parameter.Schema != null) - { - context.Write(GetDotNetType(parameter.Schema.Type, parameter.Name, parameter.IsRequired, parameter.Schema.Format)); - } - else - { - context.Write(GetDotNetType(parameter.Type, parameter.Name, parameter.IsRequired, parameter.Format)); - } - } - else if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonProperty) - { - var property = arguments[0] as JsonProperty; - context.Write(GetDotNetType(property)); - } - else if (arguments != null && arguments.Count > 2 && arguments[0] != null && arguments[1] != null && - arguments[2] != null && arguments[0] is JsonObjectType && arguments[1] is string && - arguments[2] is bool) - { - context.Write(GetDotNetType((JsonObjectType)arguments[0], (string)arguments[1], (bool)arguments[2], (string)arguments[3])); - } - else if (arguments != null && arguments.Count > 0 && arguments[0] != null) - { - context.Write($"ERROR: Expected SwaggerParameter but got {arguments[0].GetType().FullName}"); - } - else - { - context.Write($"ERROR: Expected a SwaggerParameter argument but got none."); - } - } - - private static string GetDotNetType(JsonObjectType jsonType, string name, bool required, string format) - { - if (name == "pretty" && !required) - { - return "bool?"; - } - - switch (jsonType) - { - case JsonObjectType.Boolean: - if (required) - { - return "bool"; - } - else - { - return "bool?"; - } - - case JsonObjectType.Integer: - switch (format) - { - case "int64": - if (required) - { - return "long"; - } - else - { - return "long?"; - } - - break; - case "int32": - default: - if (required) - { - return "int"; - } - else - { - return "int?"; - } - - break; - } - - case JsonObjectType.Number: - if (required) - { - return "double"; - } - else - { - return "double?"; - } - - case JsonObjectType.String: - - switch (format) - { - case "byte": - return "byte[]"; - case "date-time": - if (required) - { - return "System.DateTime"; - } - else - { - return "System.DateTime?"; - } - } - - return "string"; - case JsonObjectType.Object: - return "object"; - default: - throw new NotSupportedException(); - } - } - - private static string GetDotNetType(JsonSchema4 schema, JsonProperty parent) - { - if (schema != null) - { - if (schema.IsArray) - { - return $"IList<{GetDotNetType(schema.Item, parent)}>"; - } - - if (schema.IsDictionary && schema.AdditionalPropertiesSchema != null) - { - return $"IDictionary"; - } - - - if (schema?.Reference != null) - { - return GetClassNameForSchemaDefinition(schema.Reference); - } - else if (schema != null) - { - return GetDotNetType(schema.Type, parent.Name, parent.IsRequired, schema.Format); - } - } - - return GetDotNetType(parent.Type, parent.Name, parent.IsRequired, parent.Format); - } - - private static string GetDotNetType(JsonProperty p) - { - if (p.SchemaReference != null) - { - return GetClassNameForSchemaDefinition(p.SchemaReference); - } - else - { - if (p.IsArray) - { - // getType - return $"IList<{GetDotNetType(p.Item, p)}>"; - } - - if (p.IsDictionary && p.AdditionalPropertiesSchema != null) - { - return $"IDictionary"; - } - - return GetDotNetType(p.Type, p.Name, p.IsRequired, p.Format); - } - } - - private static void GetDotNetName(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerParameter) - { - var parameter = arguments[0] as SwaggerParameter; - context.Write(GetDotNetName(parameter.Name)); - - if (arguments.Count > 1 && arguments[1] as string == "true" && !parameter.IsRequired) - { - context.Write($" = null"); - } - } - else if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is string) - { - var style = "parameter"; - if (arguments.Count > 1) - { - style = arguments[1] as string; - } - - context.Write(GetDotNetName((string)arguments[0], style)); - } - } - - private static string GetDotNetName(string jsonName, string style = "parameter") - { - switch (style) - { - case "parameter": - if (jsonName == "namespace") - { - return "namespaceParameter"; - } - else if (jsonName == "continue") - { - return "continueParameter"; - } - - break; - - case "fieldctor": - if (jsonName == "namespace") - { - return "namespaceProperty"; - } - else if (jsonName == "continue") - { - return "continueProperty"; - } - else if (jsonName == "__referencePath") - { - return "refProperty"; - } - else if (jsonName == "default") - { - return "defaultProperty"; - } - else if (jsonName == "operator") - { - return "operatorProperty"; - } - else if (jsonName == "$schema") - { - return "schema"; - } - else if (jsonName == "enum") - { - return "enumProperty"; - } - else if (jsonName == "object") - { - return "objectProperty"; - } - else if (jsonName == "readOnly") - { - return "readOnlyProperty"; - } - else if (jsonName == "from") - { - return "fromProperty"; - } - - if (jsonName.Contains("-")) - { - return jsonName.ToCamelCase(); - } - - break; - case "field": - return GetDotNetName(jsonName, "fieldctor").ToPascalCase(); - } - - return jsonName.ToCamelCase(); - } - - private static void GetPathExpression(RenderContext context, IList arguments, - IDictionary options, RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && - arguments[0] is SwaggerOperationDescription) - { - var operation = arguments[0] as SwaggerOperationDescription; - context.Write(GetPathExpression(operation)); - } - } - - private static string GetPathExpression(SwaggerOperationDescription operation) - { - var pathExpression = operation.Path; - - if (pathExpression.StartsWith("/", StringComparison.InvariantCulture)) - { - pathExpression = pathExpression.Substring(1); - } - - pathExpression = pathExpression.Replace("{namespace}", "{namespaceParameter}"); - return pathExpression; - } - - private static void GetApiVersion(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonSchema4) - { - context.Write(GetApiVersion(arguments[0] as JsonSchema4)); - } - } - - private static void GetReturnType(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var operation = arguments?.FirstOrDefault() as SwaggerOperation; - if (operation != null) - { - string style = null; - if (arguments.Count > 1) - { - style = arguments[1] as string; - } - - context.Write(GetReturnType(operation, style)); - } - } - - private static string GetReturnType(SwaggerOperation operation, string sytle) - { - SwaggerResponse response; - - if (!operation.Responses.TryGetValue("200", out response)) - { - operation.Responses.TryGetValue("201", out response); - } - - string toType() - { - if (response != null) - { - var schema = response.Schema; - - if (schema == null) - { - return ""; - } - - if (schema.Format == "file") - { - return "Stream"; - } - - - if (schema.Reference != null) - { - return GetClassNameForSchemaDefinition(schema.Reference); - } - - return GetDotNetType(schema.Type, "", true, schema.Format); - } - - return ""; - } - - var t = toType(); - - switch (sytle) - { - case "<>": - if (t != "") - { - return "<" + t + ">"; - } - - break; - case "void": - if (t == "") - { - return "void"; - } - - break; - case "return": - if (t != "") - { - return "return"; - } - - break; - case "_result.Body": - if (t != "") - { - return "return _result.Body"; - } - - break; - default: - break; - } - - return t; - } - - private static string GetApiVersion(JsonSchema4 definition) - { - var groupVersionKindElements = (object[])definition.ExtensionData["x-kubernetes-group-version-kind"]; - var groupVersionKind = (Dictionary)groupVersionKindElements[0]; - - return groupVersionKind["version"] as string; - } - - private static void IfKindIs(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var parameter = arguments?.FirstOrDefault() as SwaggerParameter; - if (parameter != null) - { - string kind = null; - if (arguments.Count > 1) - { - kind = arguments[1] as string; - } - - if (kind == "query" && parameter.Kind == SwaggerParameterKind.Query) - { - fn(null); - } - else if (kind == "path" && parameter.Kind == SwaggerParameterKind.Path) - { - fn(null); - } - } - } - - private static void AddCurly(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var s = arguments?.FirstOrDefault() as string; - if (s != null) - { - context.Write("{" + s + "}"); - } - } - - private static void GetRequestMethod(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var s = arguments?.FirstOrDefault() as SwaggerOperationMethod?; - if (s != null) - { - context.Write(s.ToString().ToUpper()); - } - } - - private static void IfReturnType(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var operation = arguments?.FirstOrDefault() as SwaggerOperation; - if (operation != null) - { - string type = null; - if (arguments.Count > 1) - { - type = arguments[1] as string; - } - - var rt = GetReturnType(operation, "void"); - if (type == "any" && rt != "void") - { - fn(null); - } - else if (type.ToLower() == rt.ToLower()) - { - fn(null); - } - else if (type == "obj" && rt != "void" && rt != "Stream") - { - fn(null); - } - } - } - - private static void IfParamCotains(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var operation = arguments?.FirstOrDefault() as SwaggerOperation; - if (operation != null) - { - string name = null; - if (arguments.Count > 1) - { - name = arguments[1] as string; - } - - bool found = false; - - foreach (var param in operation.Parameters) - { - if (param.Name == name) - { - found = true; - break; - } - } - - if (found) - { - fn(null); - } - } - } - - private static void EscapeDataString(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var name = GetDotNetName(arguments[0] as string); - var type = arguments[1] as JsonObjectType?; - - if (name == "pretty") - { - context.Write($"{name}.Value == true ? \"true\" : \"false\""); - return; - } - - switch (type) - { - case JsonObjectType.String: - context.Write($"System.Uri.EscapeDataString({name})"); - break; - default: - context.Write($"System.Uri.EscapeDataString(SafeJsonConvert.SerializeObject({name}, SerializationSettings).Trim('\"'))"); - break; - } - } - - private static void GetModelCtorParam(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var schema = arguments[0] as JsonSchema4; - - if (schema != null) - { - context.Write(string.Join(", ", schema.Properties.Values - .OrderBy(p => !p.IsRequired) - .Select(p => - { - string sp = $"{GetDotNetType(p)} {GetDotNetName(p.Name, "fieldctor")}"; - - if (!p.IsRequired) - { - sp = $"{sp} = null"; - } - - return sp; - }))); - } - } - - private static void IfType(RenderContext context, IList arguments, IDictionary options, - RenderBlock fn, RenderBlock inverse) - { - var property = arguments?.FirstOrDefault() as JsonProperty; - if (property != null) - { - string type = null; - if (arguments.Count > 1) - { - type = arguments[1] as string; - } - - if (type == "object" && property.Reference != null && !property.IsArray && property.AdditionalPropertiesSchema == null) - { - fn(null); - } - else if (type == "objectarray" && property.IsArray && property.Item?.Reference != null) - { - fn(null); - } - } - } - - - private static string ToPascalCase(string name) - { - if (string.IsNullOrWhiteSpace(name)) - { - return name; - } - - return char.ToUpper(name[0]) + name.Substring(1); - } - - public static IEnumerable WordWrap(string text, int width) - { - var lines = text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); - foreach (var line in lines) - { - var processedLine = line.Trim(); - - // yield empty lines as they are (probably) intensional - if (processedLine.Length == 0) - { - yield return processedLine; - } - - // feast on the line until it's gone - while (processedLine.Length > 0) - { - // determine potential wrapping points - var whitespacePositions = Enumerable - .Range(0, processedLine.Length) - .Where(i => char.IsWhiteSpace(processedLine[i])) - .Concat(new[] { processedLine.Length }) - .Cast(); - var preWidthWrapAt = whitespacePositions.LastOrDefault(i => i <= width); - var postWidthWrapAt = whitespacePositions.FirstOrDefault(i => i > width); - - // choose preferred wrapping point - var wrapAt = preWidthWrapAt ?? postWidthWrapAt ?? processedLine.Length; - - // wrap - yield return processedLine.Substring(0, wrapAt); - processedLine = processedLine.Substring(wrapAt).Trim(); - } - } + [Option("versionconverter", Required = false, Default = false)] + public bool GenerateVersionConverter { get; set; } } } } diff --git a/gen/KubernetesGenerator/StringHelpers.cs b/gen/KubernetesGenerator/StringHelpers.cs new file mode 100644 index 0000000..44adb3a --- /dev/null +++ b/gen/KubernetesGenerator/StringHelpers.cs @@ -0,0 +1,128 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security; +using NJsonSchema; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class StringHelpers : INustacheHelper + { + private readonly GeneralNameHelper generalNameHelper; + + public StringHelpers(GeneralNameHelper generalNameHelper) + { + this.generalNameHelper = generalNameHelper; + } + + public void RegisterHelper() + { + Helpers.Register(nameof(ToXmlDoc), ToXmlDoc); + Helpers.Register(nameof(AddCurly), AddCurly); + Helpers.Register(nameof(EscapeDataString), EscapeDataString); + } + + private void ToXmlDoc(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is string) + { + var first = true; + + using (var reader = new StringReader(arguments[0] as string)) + { + string line = null; + while ((line = reader.ReadLine()) != null) + { + foreach (var wline in WordWrap(line, 80)) + { + if (!first) + { + context.Write("\n"); + context.Write(" /// "); + } + else + { + first = false; + } + + context.Write(SecurityElement.Escape(wline)); + } + } + } + } + } + + private static IEnumerable WordWrap(string text, int width) + { + var lines = text.Split(new[] { "\r\n", "\r", "\n" }, StringSplitOptions.None); + foreach (var line in lines) + { + var processedLine = line.Trim(); + + // yield empty lines as they are (probably) intensional + if (processedLine.Length == 0) + { + yield return processedLine; + } + + // feast on the line until it's gone + while (processedLine.Length > 0) + { + // determine potential wrapping points + var whitespacePositions = Enumerable + .Range(0, processedLine.Length) + .Where(i => char.IsWhiteSpace(processedLine[i])) + .Concat(new[] { processedLine.Length }) + .Cast(); + var preWidthWrapAt = whitespacePositions.LastOrDefault(i => i <= width); + var postWidthWrapAt = whitespacePositions.FirstOrDefault(i => i > width); + + // choose preferred wrapping point + var wrapAt = preWidthWrapAt ?? postWidthWrapAt ?? processedLine.Length; + + // wrap + yield return processedLine.Substring(0, wrapAt); + processedLine = processedLine.Substring(wrapAt).Trim(); + } + } + } + + public void AddCurly(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + var s = arguments?.FirstOrDefault() as string; + if (s != null) + { + context.Write("{" + s + "}"); + } + } + + public void EscapeDataString(RenderContext context, IList arguments, + IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + var name = generalNameHelper.GetDotNetName(arguments[0] as string); + var type = arguments[1] as JsonObjectType?; + + if (name == "pretty") + { + context.Write($"{name}.Value == true ? \"true\" : \"false\""); + return; + } + + switch (type) + { + case JsonObjectType.String: + context.Write($"System.Uri.EscapeDataString({name})"); + break; + default: + context.Write( + $"System.Uri.EscapeDataString(SafeJsonConvert.SerializeObject({name}, SerializationSettings).Trim('\"'))"); + break; + } + } + } +} diff --git a/gen/KubernetesGenerator/TypeHelper.cs b/gen/KubernetesGenerator/TypeHelper.cs new file mode 100644 index 0000000..b9ab415 --- /dev/null +++ b/gen/KubernetesGenerator/TypeHelper.cs @@ -0,0 +1,344 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using NJsonSchema; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class TypeHelper : INustacheHelper + { + private readonly ClassNameHelper classNameHelper; + + public TypeHelper(ClassNameHelper classNameHelper) + { + this.classNameHelper = classNameHelper; + } + + public void RegisterHelper() + { + Helpers.Register(nameof(GetDotNetType), GetDotNetType); + Helpers.Register(nameof(GetReturnType), GetReturnType); + Helpers.Register(nameof(IfReturnType), IfReturnType); + Helpers.Register(nameof(IfType), IfType); + } + + public void GetDotNetType(RenderContext context, IList arguments, + IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is SwaggerParameter) + { + var parameter = arguments[0] as SwaggerParameter; + + if (parameter.Schema?.Reference != null) + { + context.Write(classNameHelper.GetClassNameForSchemaDefinition(parameter.Schema.Reference)); + } + else if (parameter.Schema != null) + { + context.Write(GetDotNetType(parameter.Schema.Type, parameter.Name, parameter.IsRequired, + parameter.Schema.Format)); + } + else + { + context.Write(GetDotNetType(parameter.Type, parameter.Name, parameter.IsRequired, + parameter.Format)); + } + } + else if (arguments != null && arguments.Count > 0 && arguments[0] != null && arguments[0] is JsonProperty) + { + var property = arguments[0] as JsonProperty; + context.Write(GetDotNetType(property)); + } + else if (arguments != null && arguments.Count > 2 && arguments[0] != null && arguments[1] != null && + arguments[2] != null && arguments[0] is JsonObjectType && arguments[1] is string && + arguments[2] is bool) + { + context.Write(GetDotNetType((JsonObjectType)arguments[0], (string)arguments[1], (bool)arguments[2], + (string)arguments[3])); + } + else if (arguments != null && arguments.Count > 0 && arguments[0] != null) + { + context.Write($"ERROR: Expected SwaggerParameter but got {arguments[0].GetType().FullName}"); + } + else + { + context.Write("ERROR: Expected a SwaggerParameter argument but got none."); + } + } + + private string GetDotNetType(JsonObjectType jsonType, string name, bool required, string format) + { + if (name == "pretty" && !required) + { + return "bool?"; + } + + switch (jsonType) + { + case JsonObjectType.Boolean: + if (required) + { + return "bool"; + } + else + { + return "bool?"; + } + + case JsonObjectType.Integer: + switch (format) + { + case "int64": + if (required) + { + return "long"; + } + else + { + return "long?"; + } + + default: + if (required) + { + return "int"; + } + else + { + return "int?"; + } + } + + case JsonObjectType.Number: + if (required) + { + return "double"; + } + else + { + return "double?"; + } + + case JsonObjectType.String: + + switch (format) + { + case "byte": + return "byte[]"; + case "date-time": + if (required) + { + return "System.DateTime"; + } + else + { + return "System.DateTime?"; + } + } + + return "string"; + case JsonObjectType.Object: + return "object"; + default: + throw new NotSupportedException(); + } + } + + private string GetDotNetType(JsonSchema4 schema, JsonProperty parent) + { + if (schema != null) + { + if (schema.IsArray) + { + return $"IList<{GetDotNetType(schema.Item, parent)}>"; + } + + if (schema.IsDictionary && schema.AdditionalPropertiesSchema != null) + { + return $"IDictionary"; + } + + + if (schema?.Reference != null) + { + return classNameHelper.GetClassNameForSchemaDefinition(schema.Reference); + } + + if (schema != null) + { + return GetDotNetType(schema.Type, parent.Name, parent.IsRequired, schema.Format); + } + } + + return GetDotNetType(parent.Type, parent.Name, parent.IsRequired, parent.Format); + } + + public string GetDotNetType(JsonProperty p) + { + if (p.SchemaReference != null) + { + return classNameHelper.GetClassNameForSchemaDefinition(p.SchemaReference); + } + + if (p.IsArray) + { + // getType + return $"IList<{GetDotNetType(p.Item, p)}>"; + } + + if (p.IsDictionary && p.AdditionalPropertiesSchema != null) + { + return $"IDictionary"; + } + + return GetDotNetType(p.Type, p.Name, p.IsRequired, p.Format); + } + + public void GetReturnType(RenderContext context, IList arguments, + IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + var operation = arguments?.FirstOrDefault() as SwaggerOperation; + if (operation != null) + { + string style = null; + if (arguments.Count > 1) + { + style = arguments[1] as string; + } + + context.Write(GetReturnType(operation, style)); + } + } + + private string GetReturnType(SwaggerOperation operation, string sytle) + { + SwaggerResponse response; + + if (!operation.Responses.TryGetValue("200", out response)) + { + operation.Responses.TryGetValue("201", out response); + } + + string toType() + { + if (response != null) + { + var schema = response.Schema; + + if (schema == null) + { + return ""; + } + + if (schema.Format == "file") + { + return "Stream"; + } + + + if (schema.Reference != null) + { + return classNameHelper.GetClassNameForSchemaDefinition(schema.Reference); + } + + return GetDotNetType(schema.Type, "", true, schema.Format); + } + + return ""; + } + + var t = toType(); + + switch (sytle) + { + case "<>": + if (t != "") + { + return "<" + t + ">"; + } + + break; + case "void": + if (t == "") + { + return "void"; + } + + break; + case "return": + if (t != "") + { + return "return"; + } + + break; + case "_result.Body": + if (t != "") + { + return "return _result.Body"; + } + + break; + } + + return t; + } + + public void IfReturnType(RenderContext context, IList arguments, + IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + var operation = arguments?.FirstOrDefault() as SwaggerOperation; + if (operation != null) + { + string type = null; + if (arguments.Count > 1) + { + type = arguments[1] as string; + } + + var rt = GetReturnType(operation, "void"); + if (type == "any" && rt != "void") + { + fn(null); + } + else if (type.ToLower() == rt.ToLower()) + { + fn(null); + } + else if (type == "obj" && rt != "void" && rt != "Stream") + { + fn(null); + } + } + } + + public static void IfType(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + var property = arguments?.FirstOrDefault() as JsonProperty; + if (property != null) + { + string type = null; + if (arguments.Count > 1) + { + type = arguments[1] as string; + } + + if (type == "object" && property.Reference != null && !property.IsArray && + property.AdditionalPropertiesSchema == null) + { + fn(null); + } + else if (type == "objectarray" && property.IsArray && property.Item?.Reference != null) + { + fn(null); + } + } + } + } +} diff --git a/gen/KubernetesGenerator/UtilHelper.cs b/gen/KubernetesGenerator/UtilHelper.cs new file mode 100644 index 0000000..0343463 --- /dev/null +++ b/gen/KubernetesGenerator/UtilHelper.cs @@ -0,0 +1,52 @@ +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class UtilHelper : INustacheHelper + { + public void RegisterHelper() + { + Helpers.Register(nameof(GetTuple), GetTuple); + Helpers.Register(nameof(IfKindIs), IfKindIs); + } + + public static void GetTuple(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + if (arguments != null && arguments.Count > 0 && arguments[0] is ITuple && + options.TryGetValue("index", out var indexObj) && int.TryParse(indexObj?.ToString(), out var index)) + { + var pair = (ITuple)arguments[0]; + var value = pair[index]; + context.Write(value.ToString()); + } + } + + public static void IfKindIs(RenderContext context, IList arguments, IDictionary options, + RenderBlock fn, RenderBlock inverse) + { + var parameter = arguments?.FirstOrDefault() as SwaggerParameter; + if (parameter != null) + { + string kind = null; + if (arguments.Count > 1) + { + kind = arguments[1] as string; + } + + if (kind == "query" && parameter.Kind == SwaggerParameterKind.Query) + { + fn(null); + } + else if (kind == "path" && parameter.Kind == SwaggerParameterKind.Path) + { + fn(null); + } + } + } + } +} diff --git a/gen/KubernetesGenerator/VersionConverterGenerator.cs b/gen/KubernetesGenerator/VersionConverterGenerator.cs new file mode 100644 index 0000000..44cbd7d --- /dev/null +++ b/gen/KubernetesGenerator/VersionConverterGenerator.cs @@ -0,0 +1,52 @@ +using System; +using System.IO; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text.RegularExpressions; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class VersionConverterGenerator + { + public void GenerateFromModels(string outputDirectory) + { + // generate version converter maps + var allGeneratedModelClassNames = Directory + .EnumerateFiles(Path.Combine(outputDirectory, "Models")) + .Select(Path.GetFileNameWithoutExtension) + .ToList(); + + var versionRegex = @"(^V|v)[0-9]+((alpha|beta)[0-9]+)?"; + var typePairs = allGeneratedModelClassNames + .OrderBy(x => x) + .Select(x => new + { + Version = Regex.Match(x, versionRegex).Value?.ToLower(), + Kinda = Regex.Replace(x, versionRegex, string.Empty), + Type = x, + }) + .Where(x => !string.IsNullOrEmpty(x.Version)) + .GroupBy(x => x.Kinda) + .Where(x => x.Count() > 1) + .SelectMany(x => + x.SelectMany((value, index) => x.Skip(index + 1), (first, second) => new { first, second })) + .OrderBy(x => x.first.Kinda) + .ThenBy(x => x.first.Version) + .Select(x => (ITuple)Tuple.Create(x.first.Type, x.second.Type)) + .ToList(); + + var versionFile = + File.ReadAllText(Path.Combine(outputDirectory, "..", "Versioning", "VersionConverter.cs")); + var manualMaps = Regex.Matches(versionFile, @"\.CreateMap<(?.+?),\s?(?.+?)>") + .Select(x => Tuple.Create(x.Groups["T1"].Value, x.Groups["T2"].Value)) + .ToList(); + var versionConverterPairs = typePairs.Except(manualMaps).ToList(); + + Render.FileToFile(Path.Combine("templates", "VersionConverter.cs.template"), versionConverterPairs, + Path.Combine(outputDirectory, "VersionConverter.cs")); + Render.FileToFile(Path.Combine("templates", "ModelOperators.cs.template"), typePairs, + Path.Combine(outputDirectory, "ModelOperators.cs")); + } + } +} diff --git a/gen/KubernetesGenerator/WatchGenerator.cs b/gen/KubernetesGenerator/WatchGenerator.cs new file mode 100644 index 0000000..80c117d --- /dev/null +++ b/gen/KubernetesGenerator/WatchGenerator.cs @@ -0,0 +1,26 @@ +using System.IO; +using System.Linq; +using NSwag; +using Nustache.Core; + +namespace KubernetesGenerator +{ + public class WatchGenerator + { + public void Generate(SwaggerDocument swagger, string outputDirectory) + { + // Generate the Watcher operations + // We skip operations where the name of the class in the C# client could not be determined correctly. + // That's usually because there are different version of the same object (e.g. for deployments). + var watchOperations = swagger.Operations.Where( + o => o.Path.Contains("/watch/") + && o.Operation.ActualParameters.Any(p => p.Name == "name")).ToArray(); + + // Render. + Render.FileToFile(Path.Combine("templates", "IKubernetes.Watch.cs.template"), watchOperations, + Path.Combine(outputDirectory, "IKubernetes.Watch.cs")); + Render.FileToFile(Path.Combine("templates", "Kubernetes.Watch.cs.template"), watchOperations, + Path.Combine(outputDirectory, "Kubernetes.Watch.cs")); + } + } +} diff --git a/gen/KubernetesGenerator/IKubernetes.Watch.cs.template b/gen/KubernetesGenerator/templates/IKubernetes.Watch.cs.template similarity index 100% rename from gen/KubernetesGenerator/IKubernetes.Watch.cs.template rename to gen/KubernetesGenerator/templates/IKubernetes.Watch.cs.template diff --git a/gen/KubernetesGenerator/IKubernetes.cs.template b/gen/KubernetesGenerator/templates/IKubernetes.cs.template similarity index 100% rename from gen/KubernetesGenerator/IKubernetes.cs.template rename to gen/KubernetesGenerator/templates/IKubernetes.cs.template diff --git a/gen/KubernetesGenerator/Kubernetes.Watch.cs.template b/gen/KubernetesGenerator/templates/Kubernetes.Watch.cs.template similarity index 100% rename from gen/KubernetesGenerator/Kubernetes.Watch.cs.template rename to gen/KubernetesGenerator/templates/Kubernetes.Watch.cs.template diff --git a/gen/KubernetesGenerator/Kubernetes.cs.template b/gen/KubernetesGenerator/templates/Kubernetes.cs.template similarity index 100% rename from gen/KubernetesGenerator/Kubernetes.cs.template rename to gen/KubernetesGenerator/templates/Kubernetes.cs.template diff --git a/gen/KubernetesGenerator/KubernetesExtensions.cs.template b/gen/KubernetesGenerator/templates/KubernetesExtensions.cs.template similarity index 100% rename from gen/KubernetesGenerator/KubernetesExtensions.cs.template rename to gen/KubernetesGenerator/templates/KubernetesExtensions.cs.template diff --git a/gen/KubernetesGenerator/Model.cs.template b/gen/KubernetesGenerator/templates/Model.cs.template similarity index 100% rename from gen/KubernetesGenerator/Model.cs.template rename to gen/KubernetesGenerator/templates/Model.cs.template diff --git a/gen/KubernetesGenerator/ModelExtensions.cs.template b/gen/KubernetesGenerator/templates/ModelExtensions.cs.template similarity index 100% rename from gen/KubernetesGenerator/ModelExtensions.cs.template rename to gen/KubernetesGenerator/templates/ModelExtensions.cs.template diff --git a/gen/KubernetesGenerator/ModelOperators.cs.template b/gen/KubernetesGenerator/templates/ModelOperators.cs.template similarity index 100% rename from gen/KubernetesGenerator/ModelOperators.cs.template rename to gen/KubernetesGenerator/templates/ModelOperators.cs.template diff --git a/gen/KubernetesGenerator/VersionConverter.cs.template b/gen/KubernetesGenerator/templates/VersionConverter.cs.template similarity index 100% rename from gen/KubernetesGenerator/VersionConverter.cs.template rename to gen/KubernetesGenerator/templates/VersionConverter.cs.template