Skip to content

Instantly share code, notes, and snippets.

@thefringeninja
Created October 27, 2017 12:57
Show Gist options
  • Save thefringeninja/6df7fa16518a5c98d328331afb1ce0c0 to your computer and use it in GitHub Desktop.
Save thefringeninja/6df7fa16518a5c98d328331afb1ce0c0 to your computer and use it in GitHub Desktop.
Generate Message Contracts via CSX Script
#! "netcoreapp1.1"
#r "nuget:NetStandard.Library,1.6.1"
#r "nuget:Dotnet.Script.Core,0.13.0-beta"
#r "nuget:SharpYaml,1.6.1"
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using Dotnet.Script.Core;
using SharpYaml.Serialization;
var writer = new MessageContractWriter(new DirectoryInfo(Directory.GetCurrentDirectory()));
await writer.GenerateMessageContracts("YourNamespace");
class MessageContractWriter {
private readonly DirectoryInfo _directory;
private readonly FileInfo _output;
public MessageContractWriter(DirectoryInfo directory)
{
_directory = directory ?? throw new ArgumentNullException(nameof(directory));
if (!directory.Exists) {
throw new ArgumentException(nameof(directory));
}
_output = new FileInfo(Path.Combine(_directory.FullName, "Contracts.cs"));
}
public async Task GenerateMessageContracts(string ns) {
var model = ComposedModel.ParsePath(ns, _directory.FullName);
using (var writer = _output.CreateText()) {
await writer.WriteLineAsync("// -----------------------------------------------------------------------------------------------------------------");
await writer.WriteLineAsync("// Auto-generated code. Please do not change manually. Change the yaml file instead and rerun the text transformation.");
await writer.WriteLineAsync("// -------------------------------------------------------------------------------------------------------------------");
await writer.WriteLineAsync($"namespace {@ns}.Messages");
await writer.WriteLineAsync("{");
await writer.WriteLineAsync(" using System;");
await writer.WriteLineAsync(" using System.Runtime.Serialization;");
foreach (var namedVersionedContracts in model.NamedVersionedContracts) {
await writer.WriteLineAsync($" public static partial class {namedVersionedContracts.Key}");
await writer.WriteLineAsync(" {");
foreach (var versionedContractLookup in namedVersionedContracts) {
foreach (var versionedContract in versionedContractLookup) {
var grouping = namedVersionedContracts.Key.EndsWith("Messages")
? namedVersionedContracts.Key.Substring(0, namedVersionedContracts.Key.Length - "Messages".Length)
: namedVersionedContracts.Key;
var @namespace = ns + "." + grouping + ".V" + versionedContract.Key;
await writer.WriteLineAsync($" public static partial class V{versionedContract.Key}");
await writer.WriteLineAsync(" {");
foreach (var contract in versionedContract) {
await writer.WriteLineAsync($@" [DataContract (Namespace = ""{@namespace}"")]");
await writer.WriteLineAsync($@" public partial class {contract.Name} {contract.InheritsFrom}");
await writer.WriteLineAsync(" {");
if (contract.Properties.Any(p => p.IsArray)) {
await writer.WriteLineAsync($" public {contract.Name}()");
await writer.WriteLineAsync(" {");
foreach (var property in contract.Properties.Where(p => p.IsArray)) {
await writer.WriteLineAsync($" {property.Name} = new {property.ElementDataType}[0];");
}
await writer.WriteLineAsync(" }");
}
var count = 0;
foreach (var property in contract.Properties.Where(p => !p.Name.StartsWith("_"))) {
await writer.WriteLineAsync($" [DataMember(Order = {count})] public {property.DataType} {property.Name} {{ get; set; }}");
count++;
}
if (contract._ToString != null) {
await writer.WriteLineAsync($@" public override string ToString() => $""{contract._ToString}"";");
}
await writer.WriteLineAsync(" }");
}
await writer.WriteLineAsync(" }");
}
}
await writer.WriteLineAsync(" }");
}
await writer.WriteLineAsync("}");
}
}
}
public abstract class NamedPropertyContainer
{
private string _name;
protected NamedPropertyContainer()
{
_name = "<not_specified>";
Properties = new List<Property>();
}
public string Name
{
get { return _name; }
set
{
if(string.IsNullOrEmpty(value))
throw new ArgumentNullException("value");
_name = value;
}
}
public List<Property> Properties { get; private set; }
public int Version { get; set; }
virtual public string InheritsFrom => string.Empty;
public string _ToString { get { return Properties.Where(p => p.Name == "_ToString").Select(p => p.DataType).FirstOrDefault(); } }
}
public class Property
{
public Property()
{
_name = "<not_specified>";
_dataType = "<not_specified>";
}
private string _name;
private string _dataType;
public string Name
{
get { return _name; }
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException("value");
_name = value;
}
}
public string DataType
{
get { return _dataType; }
set
{
if (string.IsNullOrEmpty(value))
throw new ArgumentNullException("value");
_dataType = value;
}
}
public bool IsArray => DataType.EndsWith("[]");
public string ElementDataType => IsArray ? DataType.Substring(0, DataType.Length - 2) : DataType;
}
public abstract class Message : NamedPropertyContainer
{
}
public class Command : Message
{
override public string InheritsFrom => ": ICommand";
}
public class Event : Message
{
override public string InheritsFrom => ": IEvent";
}
public class DataType : NamedPropertyContainer
{
}
public class ComposedModel
{
public string Namespace { get; }
public List<NamedModel> NamedModels
{
get; set;
}
public ILookup<string, ILookup<int, NamedPropertyContainer>> NamedVersionedContracts
=> (from model in NamedModels
select new
{
model.Name,
contracts = model.Contracts.ToLookup(x => x.Version)
}).ToLookup(x => x.Name, x => x.contracts);
public ComposedModel(string @namespace)
{
Namespace = @namespace;
NamedModels = new List<NamedModel>();
}
public static ComposedModel ParsePath(string @namespace, string path)
{
if (path == null) throw new ArgumentNullException("path");
var model = new ComposedModel(@namespace);
foreach(var file in Directory.EnumerateFiles(path, "*.yaml", SearchOption.AllDirectories))
{
if (file.EndsWith("Generated.yaml")) continue;
model.NamedModels.Add(NamedModel.ParseFile(file, model.Namespace));
}
return model;
}
}
public class NamedModel
{
private readonly string _namespace;
public NamedModel(string name, string @namespace)
{
_namespace = @namespace;
Name = name;
Contracts = new List<NamedPropertyContainer>();
}
public string Name { get; }
public List<NamedPropertyContainer> Contracts { get; private set; }
public static NamedModel ParseFile(string path, string @namespace)
{
if (path == null) throw new ArgumentNullException("path");
var name = Path.GetFileNameWithoutExtension(path);
using (var yamlSource = File.OpenText(path))
{
var stream = new YamlStream();
stream.Load(yamlSource);
var visitor = new NamedModelVisitor(name, @namespace);
stream.Accept(visitor);
return visitor.Model;
}
}
}
public class NamedModelVisitor : YamlVisitor
{
private readonly NamedModel _model;
private int _mappingIndentation;
private int _scalarCount;
private NamedPropertyContainer Current => _model.Contracts[_model.Contracts.Count - 1];
private int _version;
public NamedModelVisitor(string name, string @namespace)
{
_model = new NamedModel(name, @namespace);
}
public NamedModel Model
{
get { return _model; }
}
protected override void Visit(YamlDocument document)
{
_mappingIndentation = 0;
_scalarCount = 0;
}
protected override void Visit(YamlScalarNode scalar)
{
switch (_mappingIndentation)
{
case 1:
_version = Int32.Parse(scalar.Value.Remove(0, 1));
break;
case 2:
if (scalar.Value.EndsWith("?")) // command
{
_model.Contracts.Add(new Command { Name = scalar.Value.Substring(0, scalar.Value.Length - 1), Version = _version });
}
else if (scalar.Value.EndsWith("!")) // event
{
_model.Contracts.Add(new Event { Name = scalar.Value.Substring(0, scalar.Value.Length - 1), Version = _version });
}
else // some shared thing
{
_model.Contracts.Add(new DataType { Name = scalar.Value, Version = _version });
}
break;
case 3:
switch ((_scalarCount % 2))
{
case 0:
Current.Properties.Add(new Property { Name = scalar.Value });
break;
case 1:
Current.Properties[Current.Properties.Count - 1].DataType = scalar.Value;
break;
}
_scalarCount++;
break;
}
}
protected override void Visit(YamlMappingNode mapping)
{
_mappingIndentation += 1;
_scalarCount = 0;
}
protected override void Visited(YamlMappingNode mapping)
{
_mappingIndentation -= 1;
_scalarCount = 0;
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment