Created
May 5, 2017 20:50
-
-
Save nozzlegear/925a4995bc4b8e9d04e4c72ec298bf47 to your computer and use it in GitHub Desktop.
This is a quick gist to document how I turned a few C# classes into TypeScript interfaces with NJSonSchema. I ended up replacing this program with Dogma (https://github.com/nozzlegear/dogma).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.IO; | |
using System.Linq; | |
using System.Text.RegularExpressions; | |
using KMSignalR.Schemas; | |
using Microsoft.Extensions.CommandLineUtils; | |
using NJsonSchema; | |
using NJsonSchema.CodeGeneration; | |
using NJsonSchema.CodeGeneration.TypeScript; | |
using NJsonSchema.Generation; | |
namespace KMSignalR.Generator | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
Console.WriteLine("Generating TypeScript definitions."); | |
var app = new CommandLineApplication(false); | |
app.FullName = "KMSignalR Typings Generator"; | |
app.Name = "KMSignalR.Generator"; | |
app.Description = "Generates TypeScript typings from the KMSignalR.Schemas project."; | |
app.HelpOption("-h|--help|-?"); | |
app.OnExecute(async () => | |
{ | |
var exports = new List<List<string>>(); | |
Type[] types = { | |
typeof(KMSignalR.Schemas.AbstractSessionToken), | |
typeof(KMSignalR.Schemas.AuroraOrder), | |
typeof(KMSignalR.Schemas.ArtOrder), | |
typeof(KMSignalR.Schemas.PortraitOrder), | |
}; | |
string[] autoGeneratedLines = null; | |
foreach (var type in types) | |
{ | |
var schema = await JsonSchema4.FromTypeAsync(type, new JsonSchemaGeneratorSettings() | |
{ | |
DefaultEnumHandling = EnumHandling.Integer | |
}); | |
var tsGenerator = new TypeScriptGenerator(schema, new TypeScriptGeneratorSettings() | |
{ | |
TypeStyle = TypeScriptTypeStyle.Interface, | |
TypeScriptVersion = (decimal) 2.2, | |
TypeNameGenerator = new NameGenerator(), | |
}); | |
string file = tsGenerator.GenerateFile(); | |
while (file.Contains(Environment.NewLine + Environment.NewLine)) | |
{ | |
file = file.Replace(Environment.NewLine + Environment.NewLine, Environment.NewLine); | |
} | |
var lines = file.Split(Environment.NewLine.ToCharArray()).ToList(); | |
int? startIndex = GetLineIndex(lines, "<auto-generated>"); | |
int? endIndex = GetLineIndex(lines, "</auto-generated>"); | |
if (! startIndex.HasValue || ! endIndex.HasValue || startIndex == endIndex) | |
{ | |
exports.Add(lines); | |
continue; | |
} | |
if (autoGeneratedLines == null) | |
{ | |
autoGeneratedLines = lines.Skip(startIndex.Value).Take(endIndex.Value - startIndex.Value + 1).ToArray(); | |
} | |
int skip = endIndex.Value + 3; // Skip the </auto-generated>, newline, and //---- comment. | |
exports.Add(lines.Skip(skip).ToList()); | |
} | |
File.WriteAllText("./typings/kmsignalr.generated.d.ts", ConcatToFile("kmsignalr/models", exports, autoGeneratedLines)); | |
return 0; | |
}); | |
app.Execute(args); | |
} | |
/// <summary> | |
/// Attempts to find the index of a line with the given string. | |
/// </summary> | |
private static int? GetLineIndex(List<string> lines, string searchFor) | |
{ | |
var matches = lines.Where(l => l.Contains(searchFor)); | |
if (matches.Count() == 0) | |
{ | |
return null; | |
} | |
return lines.IndexOf(matches.First()); | |
} | |
/// <summary> | |
/// Replaces empty lines in a list of strings. | |
/// </summary> | |
private static IEnumerable<string> ReplaceEmptyLines(IEnumerable<string> lines) | |
{ | |
return lines.Where(line => ! string.IsNullOrEmpty(line)); | |
} | |
/// <summary> | |
/// Finds duplicate 'export X' statements in a list of typescript lines, replacing the duplicates and leaving the original export. | |
/// </summary> | |
private static IEnumerable<string> FindDuplicateExports(IEnumerable<IEnumerable<string>> lines) | |
{ | |
var allLines = lines.SelectMany(list => list).ToList(); | |
var duplicates = allLines.Select((line, index) => new { Value = line, Index = index }) | |
.Where(line => line.Value.Contains("export") && line.Value.Contains("{")) | |
.Where(line => allLines.Count(sourceLine => sourceLine == line.Value) > 1); | |
var ranges = new List<int>(); | |
foreach (var export in duplicates.GroupBy(dupe => dupe.Value).Select(dupe => dupe.First())) | |
{ | |
// Remove all duplicates except the first. | |
foreach (var duplicate in duplicates.Where(dupe => dupe.Value == export.Value).Skip(1)) | |
{ | |
int endIndex = allLines.IndexOf("}", duplicate.Index); | |
ranges.AddRange(Enumerable.Range(duplicate.Index, endIndex - duplicate.Index + 1)); | |
} | |
} | |
return allLines.Where((_, index) => ! ranges.Contains(index)).ToList(); | |
} | |
private static string ConcatToFile(string moduleName, IEnumerable<IEnumerable<string>> exports, IEnumerable<string> autoGeneratedWarning = null) | |
{ | |
autoGeneratedWarning = autoGeneratedWarning ?? new string[] {}; | |
string output = string.Join(Environment.NewLine, ReplaceEmptyLines(autoGeneratedWarning)); | |
output += Environment.NewLine; | |
output += $"declare module \"{moduleName}\" {{"; | |
output += Environment.NewLine; | |
output += string.Join("\n", ReplaceEmptyLines(FindDuplicateExports(exports)).Select(l => "\t" + l)); | |
output += Environment.NewLine; | |
output += "}"; | |
return output; | |
} | |
} | |
class NameGenerator : DefaultTypeNameGenerator | |
{ | |
public override string Generate(JsonSchema4 schema, string typeNameHint, ICollection<string> reservedTypeNames) | |
{ | |
string prefix = "Abstract"; | |
if (typeNameHint.StartsWith(prefix)) | |
{ | |
return base.Generate(schema, typeNameHint.Substring(prefix.Length), reservedTypeNames); | |
} | |
return base.Generate(schema, typeNameHint, reservedTypeNames); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment