Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 11 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,17 @@ csharp_space_between_method_call_parameter_list_parentheses = false:error
csharp_preserve_single_line_statements = false:error
csharp_preserve_single_line_blocks = true:error

# Resharper
resharper_csharp_braces_for_lock=required_for_complex
resharper_csharp_braces_for_using=required_for_complex
resharper_csharp_braces_for_while=required_for_complex
resharper_csharp_braces_for_foreach=required_for_complex
resharper_csharp_braces_for_for=required_for_complex
resharper_csharp_braces_for_fixed=required_for_complex
resharper_csharp_braces_for_ifelse=required_for_complex

resharper_csharp_accessor_owner_body=expression_body

# Override source included files
[{SimpleJson|SynchronizedCollection}.cs]
csharp_style_var_for_built_in_types = false:none
Expand Down
119 changes: 58 additions & 61 deletions src/CodeGeneration/ApiGenerator/ApiGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,9 @@
using System.IO;
using System.Linq;
using ApiGenerator.Domain;
using Newtonsoft.Json;
using ShellProgressBar;
using Newtonsoft.Json.Linq;
using RazorLight;
using ShellProgressBar;

namespace ApiGenerator
{
Expand All @@ -17,39 +16,6 @@ public class ApiGenerator
.UseMemoryCachingProvider()
.Build();

public static void Generate(string downloadBranch, params string[] folders)
{
Warnings = new List<string>();
var spec = CreateRestApiSpecModel(downloadBranch, folders);
var actions = new Dictionary<Action<RestApiSpec>, string>
{
{ GenerateClientInterface, "Client interface" },
{ GenerateRequestParameters, "Request parameters" },
{ GenerateDescriptors, "Descriptors" },
{ GenerateRequests, "Requests" },
{ GenerateEnums, "Enums" },
{ GenerateRawClient, "Lowlevel client" },
{ GenerateRawDispatch, "Dispatch" },
};

using (var pbar = new ProgressBar(actions.Count, "Generating code", new ProgressBarOptions { BackgroundColor = ConsoleColor.DarkGray }))
{
foreach(var kv in actions)
{
pbar.Message = "Generating " + kv.Value;
kv.Key(spec);
pbar.Tick("Generated " + kv.Value);
}
}

if (Warnings.Count == 0) return;

Console.ForegroundColor = ConsoleColor.Yellow;
foreach (var warning in Warnings.Distinct().OrderBy(w=>w))
Console.WriteLine(warning);
Console.ResetColor();
}

public static List<string> Warnings { get; private set; }

private static string[] IgnoredApis { get; } =
Expand Down Expand Up @@ -85,27 +51,63 @@ public static void Generate(string downloadBranch, params string[] folders)
"nodes.reload_secure_settings.json"
};

public static void Generate(string downloadBranch, params string[] folders)
{
Warnings = new List<string>();
var spec = CreateRestApiSpecModel(downloadBranch, folders);
var actions = new Dictionary<Action<RestApiSpec>, string>
{
{ GenerateClientInterface, "Client interface" },
{ GenerateRequestParameters, "Request parameters" },
{ GenerateDescriptors, "Descriptors" },
{ GenerateRequests, "Requests" },
{ GenerateEnums, "Enums" },
{ GenerateRawClient, "Lowlevel client" },
{ GenerateRawDispatch, "Dispatch" },
};

using (var pbar = new ProgressBar(actions.Count, "Generating code", new ProgressBarOptions { BackgroundColor = ConsoleColor.DarkGray }))
{
foreach (var kv in actions)
{
pbar.Message = "Generating " + kv.Value;
kv.Key(spec);
pbar.Tick("Generated " + kv.Value);
}
}

if (Warnings.Count == 0) return;

Console.ForegroundColor = ConsoleColor.Yellow;
foreach (var warning in Warnings.Distinct().OrderBy(w => w))
Console.WriteLine(warning);
Console.ResetColor();
}

private static RestApiSpec CreateRestApiSpecModel(string downloadBranch, string[] folders)
{
var directories = Directory.GetDirectories(CodeConfiguration.RestSpecificationFolder, "*", SearchOption.AllDirectories)
.Where(f=>folders == null || folders.Length == 0 || folders.Contains(new DirectoryInfo(f).Name))
.Where(f => folders == null || folders.Length == 0 || folders.Contains(new DirectoryInfo(f).Name))
.ToList();

var endpoints = new Dictionary<string, ApiEndpoint>();
using (var pbar = new ProgressBar(directories.Count, $"Listing {directories.Count} directories", new ProgressBarOptions { BackgroundColor = ConsoleColor.DarkGray }))
using (var pbar = new ProgressBar(directories.Count, $"Listing {directories.Count} directories",
new ProgressBarOptions { BackgroundColor = ConsoleColor.DarkGray }))
{
var folderFiles = directories.Select(dir =>
Directory.GetFiles(dir)
.Where(f => f.EndsWith(".json") && !IgnoredApis.Contains(new FileInfo(f).Name))
.ToList()
.Where(f => f.EndsWith(".json") && !IgnoredApis.Contains(new FileInfo(f).Name))
.ToList()
);
var commonFile = Path.Combine(CodeConfiguration.RestSpecificationFolder, "Core", "_common.json");
if (!File.Exists(commonFile)) throw new Exception($"Expected to find {commonFile}");

RestApiSpec.CommonApiQueryParameters = CreateCommonApiQueryParameters(commonFile);

foreach (var jsonFiles in folderFiles)
{
using (var fileProgress = pbar.Spawn(jsonFiles.Count, $"Listing {jsonFiles.Count} files", new ProgressBarOptions { ProgressCharacter = '─', BackgroundColor = ConsoleColor.DarkGray }))
using (var fileProgress = pbar.Spawn(jsonFiles.Count, $"Listing {jsonFiles.Count} files",
new ProgressBarOptions { ProgressCharacter = '─', BackgroundColor = ConsoleColor.DarkGray }))
{
foreach (var file in jsonFiles)
{
Expand All @@ -118,6 +120,7 @@ private static RestApiSpec CreateRestApiSpecModel(string downloadBranch, string[
var endpoint = CreateApiEndpoint(file);
endpoints.Add(endpoint.Key, endpoint.Value);
}

fileProgress.Tick();
}
}
Expand Down Expand Up @@ -169,58 +172,51 @@ private static void PatchOfficialSpec(JObject original, string jsonFile)
MergeArrayHandling = MergeArrayHandling.Union
});

if (pathsOverride != null)
{
original.SelectToken("*.url.paths").Replace(pathsOverride);
}



if (pathsOverride != null) original.SelectToken("*.url.paths").Replace(pathsOverride);
}

private static Dictionary<string, ApiQueryParameters> CreateCommonApiQueryParameters(string jsonFile)
{
var json = File.ReadAllText(jsonFile);
var jobject = JObject.Parse(json);
var commonParameters = jobject.Property("params").Value.ToObject<Dictionary<string, ApiQueryParameters>>();
return ApiQueryParametersPatcher.Patch(null, commonParameters, null, checkCommon: false);
return ApiQueryParametersPatcher.Patch(null, commonParameters, null, false);
}

private static string CreateMethodName(string apiEndpointKey)
{
return PascalCase(apiEndpointKey);
}
private static string CreateMethodName(string apiEndpointKey) => PascalCase(apiEndpointKey);

private static string DoRazor(string name, string template, RestApiSpec model)
{
return Razor.CompileRenderAsync(name, template, model).GetAwaiter().GetResult();
}
private static string DoRazor(string name, string template, RestApiSpec model) =>
Razor.CompileRenderAsync(name, template, model).GetAwaiter().GetResult();

private static void GenerateClientInterface(RestApiSpec model)
{
var targetFile = CodeConfiguration.EsNetFolder + @"IElasticLowLevelClient.Generated.cs";
var source = DoRazor(nameof(GenerateClientInterface), File.ReadAllText(CodeConfiguration.ViewFolder + @"IElasticLowLevelClient.Generated.cshtml"), model);
var source = DoRazor(nameof(GenerateClientInterface),
File.ReadAllText(CodeConfiguration.ViewFolder + @"IElasticLowLevelClient.Generated.cshtml"), model);
File.WriteAllText(targetFile, source);
}

private static void GenerateRawDispatch(RestApiSpec model)
{
var targetFile = CodeConfiguration.NestFolder + @"_Generated/_LowLevelDispatch.generated.cs";
var source = DoRazor(nameof(GenerateRawDispatch), File.ReadAllText(CodeConfiguration.ViewFolder + @"_LowLevelDispatch.Generated.cshtml"), model);
var source = DoRazor(nameof(GenerateRawDispatch), File.ReadAllText(CodeConfiguration.ViewFolder + @"_LowLevelDispatch.Generated.cshtml"),
model);
File.WriteAllText(targetFile, source);
}

private static void GenerateRawClient(RestApiSpec model)
{
var targetFile = CodeConfiguration.EsNetFolder + @"ElasticLowLevelClient.Generated.cs";
var source = DoRazor(nameof(GenerateRawClient), File.ReadAllText(CodeConfiguration.ViewFolder + @"ElasticLowLevelClient.Generated.cshtml"), model);
var source = DoRazor(nameof(GenerateRawClient),
File.ReadAllText(CodeConfiguration.ViewFolder + @"ElasticLowLevelClient.Generated.cshtml"), model);
File.WriteAllText(targetFile, source);
}

private static void GenerateDescriptors(RestApiSpec model)
{
var targetFile = CodeConfiguration.NestFolder + @"_Generated\_Descriptors.generated.cs";
var source = DoRazor(nameof(GenerateDescriptors), File.ReadAllText(CodeConfiguration.ViewFolder + @"_Descriptors.Generated.cshtml"), model);
var source = DoRazor(nameof(GenerateDescriptors), File.ReadAllText(CodeConfiguration.ViewFolder + @"_Descriptors.Generated.cshtml"),
model);
File.WriteAllText(targetFile, source);
}

Expand All @@ -234,7 +230,8 @@ private static void GenerateRequests(RestApiSpec model)
private static void GenerateRequestParameters(RestApiSpec model)
{
var targetFile = CodeConfiguration.EsNetFolder + @"Domain\RequestParameters\RequestParameters.Generated.cs";
var source = DoRazor(nameof(GenerateRequestParameters), File.ReadAllText(CodeConfiguration.ViewFolder + @"RequestParameters.Generated.cshtml"), model);
var source = DoRazor(nameof(GenerateRequestParameters),
File.ReadAllText(CodeConfiguration.ViewFolder + @"RequestParameters.Generated.cshtml"), model);
File.WriteAllText(targetFile, source);
}

Expand Down
73 changes: 38 additions & 35 deletions src/CodeGeneration/ApiGenerator/CodeConfiguration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,68 +8,71 @@ namespace ApiGenerator
{
public static class CodeConfiguration
{
public static readonly Assembly Assembly = typeof(ApiGenerator).Assembly;

private static string _root = null;

private static string Root
{
get
{
if (CodeConfiguration._root != null) return CodeConfiguration._root;
var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory());

var runningAsDnx =
directoryInfo.Name == "ApiGenerator" &&
directoryInfo.Parent != null &&
directoryInfo.Parent.Name == "CodeGeneration";

CodeConfiguration._root = runningAsDnx ? "" : @"..\..\..\";
return CodeConfiguration._root;
}
}

public static string NestFolder { get; } = $@"{Root}..\..\..\src\Nest\";
public static string EsNetFolder { get; } = $@"{Root}..\..\..\src\Elasticsearch.Net\";
public static string ViewFolder { get; } = $@"{Root}Views\";
public static string RestSpecificationFolder { get; } = $@"{Root}RestSpecification\";
public static string LastDownloadedVersionFile { get; } = Path.Combine(Root, "last_downloaded_version.txt");
public static readonly Assembly Assembly = typeof(ApiGenerator).Assembly;

public static readonly Dictionary<string, string> ApiNameMapping =
(from f in new DirectoryInfo(NestFolder).GetFiles("*.cs", SearchOption.AllDirectories)
let contents = File.ReadAllText(f.FullName)
let c = Regex.Replace(contents, @"^.+\[MapsApi\(""([^ \r\n]+)""\)\].*$", "$1", RegexOptions.Singleline)
where !c.Contains(" ") //filter results that did not match
select new { Value = f.Name.Replace("Request", ""), Key = c.Replace(".json", "") })
.DistinctBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value.Replace(".cs", ""));
.DistinctBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value.Replace(".cs", ""));


public static readonly Dictionary<string, string> MethodNameOverrides =
(from f in new DirectoryInfo(NestFolder).GetFiles("*.cs", SearchOption.AllDirectories)
let contents = File.ReadAllText(f.FullName)
let c = Regex.Replace(contents, @"^.+\[DescriptorFor\(""([^ \r\n]+)""\)\].*$", "$1", RegexOptions.Singleline)
where !c.Contains(" ") //filter results that did not match
select new { Value = f.Name.Replace("Request", ""), Key = c })
.DistinctBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value.Replace(".cs", ""));
.DistinctBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value.Replace(".cs", ""));

public static readonly Dictionary<string, string> KnownDescriptors =
public static readonly Dictionary<string, string> KnownDescriptors =
(from f in new DirectoryInfo(NestFolder).GetFiles("*Request.cs", SearchOption.AllDirectories)
let contents = File.ReadAllText(f.FullName)
let c = Regex.Replace(contents, @"^.+class ([^ \r\n]+Descriptor(?:<[^>\r\n]+>)?[^ \r\n]*).*$", "$1", RegexOptions.Singleline)
select new { Key = Regex.Replace(c, "<.*$", ""), Value = Regex.Replace(c, @"^.*?(?:(\<.+>).*?)?$", "$1") })
.DistinctBy(v => v.Key)
.OrderBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value);
.DistinctBy(v => v.Key)
.OrderBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value);

public static readonly Dictionary<string, string> KnownRequests =
public static readonly Dictionary<string, string> KnownRequests =
(from f in new DirectoryInfo(NestFolder).GetFiles("*Request.cs", SearchOption.AllDirectories)
let contents = File.ReadAllText(f.FullName)
let c = Regex.Replace(contents, @"^.+interface ([^ \r\n]+Request(?:<[^>\r\n]+>)?[^ \r\n]*).*$", "$1", RegexOptions.Singleline)
where c.StartsWith("I") && c.Contains("Request")
select new { Key = Regex.Replace(c, "<.*$", ""), Value = Regex.Replace(c, @"^.*?(?:(\<.+>).*?)?$", "$1") })
.DistinctBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value);
.DistinctBy(v => v.Key)
.ToDictionary(k => k.Key, v => v.Value);


public static string EsNetFolder { get; } = $@"{Root}..\..\..\src\Elasticsearch.Net\";
public static string LastDownloadedVersionFile { get; } = Path.Combine(Root, "last_downloaded_version.txt");

public static string NestFolder { get; } = $@"{Root}..\..\..\src\Nest\";
public static string RestSpecificationFolder { get; } = $@"{Root}RestSpecification\";
public static string ViewFolder { get; } = $@"{Root}Views\";

private static string Root
{
get
{
if (_root != null) return _root;

var directoryInfo = new DirectoryInfo(Directory.GetCurrentDirectory());

var runningAsDnx =
directoryInfo.Name == "ApiGenerator" &&
directoryInfo.Parent != null &&
directoryInfo.Parent.Name == "CodeGeneration";

_root = runningAsDnx ? "" : @"..\..\..\";
return _root;
}
}
}
}
24 changes: 18 additions & 6 deletions src/CodeGeneration/ApiGenerator/CodeGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,10 @@ public static string Property(string type, string name, string key, string sette
A(PropertyGenerator(type, name, key, setter));
return string.Join("\r\n\t\t", components);

void A(string s) => components.Add(s);
void A(string s)
{
components.Add(s);
}
}

public static string Constructor(Constructor c)
Expand All @@ -32,9 +35,12 @@ public static string Constructor(Constructor c)
if (!c.Body.IsNullOrEmpty()) A(c.Body);
if (!c.AdditionalCode.IsNullOrEmpty()) A(c.AdditionalCode);
return string.Join("\r\n\t\t", components);
void A(string s) => components.Add(s);
}

void A(string s)
{
components.Add(s);
}
}


private static IEnumerable<string> RenderDocumentation(params string[] doc)
Expand All @@ -44,24 +50,30 @@ private static IEnumerable<string> RenderDocumentation(params string[] doc)
{
case 0: yield break;
case 1:
yield return ($"///<summary>{doc[0]}</summary>");
yield return $"///<summary>{doc[0]}</summary>";

yield break;
default:
yield return "///<summary>";

foreach (var d in doc) yield return $"/// {d}";

yield return "///</summary>";

yield break;
}
}

private static string[] WrapDocumentation(string documentation)
{
const int max = 140;
var lines = documentation.Split(new[] {' '}, StringSplitOptions.RemoveEmptyEntries);
var lines = documentation.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
var charCount = 0;
return lines.GroupBy(Wrap).Select(l => string.Join(" ", l.ToArray())).ToArray();

int Wrap(string w)
{
var increase = (charCount % max + w.Length + 1 >= max ? max - (charCount % max) : 0);
var increase = charCount % max + w.Length + 1 >= max ? max - charCount % max : 0;
return (charCount += increase + w.Length + 1) / max;
}
}
Expand Down
Loading