Skip to content
Snippets Groups Projects
Program.cs 24 KiB
Newer Older
  • Learn to ignore specific revisions
  • using System;
    
    using System.Collections.Generic;
    
    using System.Collections.ObjectModel;
    
    using System.IO;
    using System.Linq;
    using System.Text;
    
    
    namespace MasterDevs.ChromeDevTools.ProtocolGenerator
    
    brewdente's avatar
    brewdente committed
        internal class Program
    
    brewdente's avatar
    brewdente committed
            private const string CommandAttribute = "Command";
            private const string CommandResponseAttribute = "CommandResponse";
            private const string EventAttribute = "Event";
            private const string ProtocolNameClass = "ProtocolName";
            private const string RootNamespace = "MasterDevs.ChromeDevTools.Protocol";
            private const string CommandSubclass = "Command";
            private const string CommandResponseSubclass = CommandSubclass + "Response";
            private const string EventSubclass = "Event";
            private static Dictionary<string, Dictionary<string, string>> _DomainPropertyTypes = new Dictionary<string, Dictionary<string, string>>();
    
            private static Dictionary<string, List<string>> _DomainCommands = new Dictionary<string, List<string>>();
            private static Dictionary<string, List<string>> _DomainEvents = new Dictionary<string, List<string>>();
            private static Dictionary<string, string> _SimpleTypes = new Dictionary<string, string>();
    
    
    brewdente's avatar
    brewdente committed
            private static void Main(string[] args)
    
                // At this point in time, we only process the most recent Chrome
                // and iOS (Safari) protocols.
    
                Dictionary<string, string[]> protocolFiles = new Dictionary<string, string[]>
                {
                    {"Chrome", new [] { "js_protocol.json", "browser_protocol.json" } },
                    {"iOS", new [] { "Inspector-iOS-9.3.json" } }
                };
    
    
    
                //protocolFiles.Add("Chrome-0.1", "Inspector-0.1.json");
                //protocolFiles.Add("Chrome-1.0", "Inspector-1.0.json");
    
                //protocolFiles.Add("Chrome", "Inspector-1.1.json");
    
                //protocolFiles.Add("iOS-7.0", "Inspector-iOS-7.0.json");
                //protocolFiles.Add("iOS-8.0", "Inspector-iOS-8.0.json");
                //protocolFiles.Add("iOS-9.0", "Inspector-iOS-9.0.json");
    
    
                Collection<Protocol> protocols = new Collection<Protocol>();
    
    
                // "Explicit mappings" allow us to map one type reference to another. This is a
                // rather hard-coded way of doing things, and is only used when the same type
                // has different names accross different versions of the dev tools - e.g. the RGBA
                // type which is named RGBAColor for Safari.
                Dictionary<string, string> explicitMappings = new Dictionary<string, string>();
    
                foreach (var protocolFile in protocolFiles)
    
    Frederik Carlier's avatar
    Frederik Carlier committed
                    Protocol p = ProtocolProcessor.LoadProtocol(protocolFile.Value, protocolFile.Key);
    
                    ProtocolProcessor.ResolveTypeReferences(p, explicitMappings);
    
    Frederik Carlier's avatar
    Frederik Carlier committed
                    protocols.Add(p);
    
                var outputFolder = "OutputProtocol";
                if (args.Length > 0)
                {
                    outputFolder = args[0];
                }
    
    brewdente's avatar
    brewdente committed
                if (Directory.Exists(outputFolder))
    
                {
                    Directory.Delete(outputFolder, true);
    
                    Directory.CreateDirectory(outputFolder);
                }
    
                foreach (var protocol in protocols)
                {
                    var outputDirectoryInfo = Directory.CreateDirectory(Path.Combine(outputFolder, protocol.Alias));
                    WriteProtocolClasses(protocol, outputDirectoryInfo);
    
            private static void WriteProtocolClasses(Protocol protocolObject, DirectoryInfo directory)
    
                _DomainPropertyTypes.Clear();
                _DomainCommands.Clear();
                _DomainEvents.Clear();
                _SimpleTypes.Clear();
    
    
                foreach (var domain in domains)
                {
    
                    AddPropertyTypes(domain.Name, domain.Types);
    
    brewdente's avatar
    brewdente committed
                foreach (var domain in domains)
    
                    var domainName = domain.Name;
                    var types = domain.Types;
                    var commands = domain.Commands;
                    var events = domain.Events;
    
                    _DomainCommands[domainName] = new List<string>();
                    _DomainEvents[domainName] = new List<string>();
    
                    WriteProtocolClasses(directory, protocolObject.Alias, domainName, types, commands, events);
    
                WriteMethodConstants(directory, protocolObject.Alias);
    
            private static void AddPropertyTypes(string domain, IEnumerable<Type> types)
    
            {
                var domainDictionary = new Dictionary<string, string>();
                _DomainPropertyTypes[domain] = domainDictionary;
    
                    var propertyType = type.Kind;
                    var typeName = type.Name;
    
    Frederik Carlier's avatar
    Frederik Carlier committed
                    if (type.IsEnum()
                        || type.IsClass()
                        || type.IsObject())
    
                    {
                        propertyType = domain + "." + typeName;
                    }
                    if ("Network" == domain && "Headers" == typeName)
                    {
                        domainDictionary[typeName] = "Dictionary<string, string>";
                    }
                    else
                    {
                        domainDictionary[typeName] = GeneratePropertyType(propertyType);
                    }
                    if ("array" == propertyType)
                    {
                        AddArrayPropertyType(domainDictionary, domain, type);
                    }
                }
            }
    
    
            private static void AddArrayPropertyType(Dictionary<string, string> domainDictionary, string domain, Type type)
    
                var itemsType = GeneratePropertyType(items.Kind);
    
                if (String.IsNullOrEmpty(itemsType))
                {
    
                if (IsGeneratedNativeType(itemsType))
                    domainDictionary[type.Name] = itemsType + "[]";
                else
                    domainDictionary[type.Name] = domain + "." + itemsType + "[]";
    
            private static void WriteProtocolClasses(DirectoryInfo directory, string ns, string domainName, IEnumerable<Type> types, IEnumerable<Command> commands, IEnumerable<Event> events)
    
            {
                var domainDirectoryInfo = CreateDomainFolder(directory, domainName);
    
                    WriteCommand(domainDirectoryInfo, ns, command);
    
    brewdente's avatar
    brewdente committed
    
    
            private static void WriteMethodConstants(DirectoryInfo domainDirectoryInfo, string ns)
    
                sb.AppendFormat("using MasterDevs.ChromeDevTools;");
    
                sb.AppendLine();
                sb.AppendLine();
    
                sb.AppendFormat("namespace {0}.{1}", RootNamespace, ns);
    
                sb.AppendLine();
                sb.AppendLine("{");
                sb.AppendFormat("\tpublic static class {0}", ProtocolNameClass);
                sb.AppendLine();
                sb.AppendLine("\t{");
    
                var domains = _DomainCommands.Keys.Union(_DomainEvents.Keys).Distinct();
    
    brewdente's avatar
    brewdente committed
                foreach (var domain in domains)
    
                {
                    sb.AppendFormat("\t\tpublic static class {0}", domain);
                    sb.AppendLine();
                    sb.AppendLine("\t\t{");
                    List<string> commands;
    
    brewdente's avatar
    brewdente committed
                    if (_DomainCommands.TryGetValue(domain, out commands))
    
    brewdente's avatar
    brewdente committed
                        foreach (var commandName in commands)
    
                        {
                            sb.AppendFormat("\t\t\tpublic const string {0} = \"{1}.{2}\";", ToCamelCase(commandName), domain, commandName);
                            sb.AppendLine();
                        }
                    }
                    List<string> events;
    
    brewdente's avatar
    brewdente committed
                    if (_DomainEvents.TryGetValue(domain, out events))
    
    brewdente's avatar
    brewdente committed
                        foreach (var eventName in events)
    
                        {
                            sb.AppendFormat("\t\t\tpublic const string {0} = \"{1}.{2}\";", ToCamelCase(eventName), domain, eventName);
                            sb.AppendLine();
                        }
                    }
                    sb.AppendLine("\t\t}");
                    sb.AppendLine();
                }
    
                sb.AppendLine("\t}");
                sb.AppendLine("}");
                WriteToFile(domainDirectoryInfo, ProtocolNameClass, sb.ToString());
            }
    
    
            private static void WriteEvent(DirectoryInfo domainDirectoryInfo, string ns, Event evnt)
    
                var eventName = evnt.Name;
                var description = evnt.Description;
                var parameters = evnt.Parameters;
    
                // ignoreing "handlers" ... i'm not sure what they are for yet
                _DomainEvents[domainDirectoryInfo.Name].Add(eventName);
    
                WriteEvent(domainDirectoryInfo, ns, eventName, description, parameters, evnt.SupportedBy);
    
            private static void WriteEvent(DirectoryInfo domainDirectoryInfo, string ns, string eventName, string description, IEnumerable<Property> parameters, IEnumerable<string> supportedBy)
    
            {
                var className = ToCamelCase(eventName) + EventSubclass;
                var sb = new StringBuilder();
    
                sb.AppendFormat("using MasterDevs.ChromeDevTools;");
    
                sb.AppendFormat("using Newtonsoft.Json;");
    
                sb.AppendLine();
                sb.AppendLine();
    
                sb.AppendFormat("namespace {0}.{1}.{2}", RootNamespace, ns, domainDirectoryInfo.Name);
    
                sb.AppendLine();
                sb.AppendLine("{");
                if (!String.IsNullOrEmpty(description))
                {
                    sb.AppendLine("\t/// <summary>");
                    sb.AppendFormat("\t/// {0}", description);
                    sb.AppendLine();
                    sb.AppendLine("\t/// </summary>");
                }
                sb.AppendFormat("\t[{0}({1}.{2}.{3})]", EventAttribute, ProtocolNameClass, domainDirectoryInfo.Name, ToCamelCase(eventName));
                sb.AppendLine();
    
                WriteSupportedBy(sb, supportedBy);
    
                sb.AppendFormat("\tpublic class {0}", className);
                sb.AppendLine();
                sb.AppendLine("\t{");
    
                foreach (var parameterProperty in parameters)
    
                    WriteProperty(sb, domainDirectoryInfo.Name, className, parameterProperty);
    
                }
                sb.AppendLine("\t}");
                sb.AppendLine("}");
                WriteToFile(domainDirectoryInfo, className, sb.ToString());
            }
    
    
            private static void WriteCommand(DirectoryInfo domainDirectoryInfo, string ns, Command command)
    
                var commandName = command.Name;
                var description = command.Description;
                var parameters = command.Parameters;
                var returnObject = command.Returns;
    
                _DomainCommands[domainDirectoryInfo.Name].Add(commandName);
    
                WriteCommand(domainDirectoryInfo, ns, commandName, description, parameters, command.SupportedBy);
                WriteCommandResponse(domainDirectoryInfo, ns, commandName, description, returnObject, command.SupportedBy);
    
            private static void WriteCommandResponse(DirectoryInfo domainDirectoryInfo, string ns, string commandName, string description, IEnumerable<Property> returnObject, IEnumerable<string> supportedBy)
    
            {
                var className = ToCamelCase(commandName) + CommandResponseSubclass;
                var sb = new StringBuilder();
    
                sb.AppendLine("using MasterDevs.ChromeDevTools;");
    
                sb.AppendLine("using Newtonsoft.Json;");
                sb.AppendLine("using System.Collections.Generic;");
                sb.AppendLine();
    
                sb.AppendFormat("namespace {0}.{1}.{2}", RootNamespace, ns, domainDirectoryInfo.Name);
    
                sb.AppendLine();
                sb.AppendLine("{");
    
    brewdente's avatar
    brewdente committed
                if (!String.IsNullOrEmpty(description))
                {
    
                    sb.AppendLine("\t/// <summary>");
                    sb.AppendFormat("\t/// {0}", description);
                    sb.AppendLine();
                    sb.AppendLine("\t/// </summary>");
                }
                sb.AppendFormat("\t[{0}({1}.{2}.{3})]", CommandResponseAttribute, ProtocolNameClass, domainDirectoryInfo.Name, ToCamelCase(commandName));
                sb.AppendLine();
    
                WriteSupportedBy(sb, supportedBy);
    
                sb.AppendFormat("\tpublic class {0}", className);
                sb.AppendLine();
                sb.AppendLine("\t{");
    
                foreach (var returnObjectProperty in returnObject)
    
                    WriteProperty(sb, domainDirectoryInfo.Name, className, returnObjectProperty);
    
                }
                sb.AppendLine("\t}");
                sb.AppendLine("}");
                WriteToFile(domainDirectoryInfo, className, sb.ToString());
            }
    
    
            private static void WriteCommand(DirectoryInfo domainDirectoryInfo, string ns, string commandName, string description, IEnumerable<Property> parameters, IEnumerable<string> supportedBy)
    
            {
                var className = ToCamelCase(commandName) + CommandSubclass;
    
                var responseClassName = ToCamelCase(commandName) + CommandResponseSubclass;
    
                var sb = new StringBuilder();
    
                sb.AppendFormat("using MasterDevs.ChromeDevTools;");
    
                sb.AppendLine("using Newtonsoft.Json;");
                sb.AppendLine("using System.Collections.Generic;");
                sb.AppendLine();
    
                sb.AppendFormat("namespace {0}.{1}.{2}", RootNamespace, ns, domainDirectoryInfo.Name);
    
                sb.AppendLine();
                sb.AppendLine("{");
                if (!String.IsNullOrEmpty(description))
                {
                    sb.AppendLine("\t/// <summary>");
                    sb.AppendFormat("\t/// {0}", description);
                    sb.AppendLine();
                    sb.AppendLine("\t/// </summary>");
                }
                sb.AppendFormat("\t[{0}({1}.{2}.{3})]", CommandAttribute, ProtocolNameClass, domainDirectoryInfo.Name, ToCamelCase(commandName));
                sb.AppendLine();
    
                WriteSupportedBy(sb, supportedBy);
    
                sb.AppendFormat("\tpublic class {0}: ICommand<{1}>", className, responseClassName);
    
                sb.AppendLine();
                sb.AppendLine("\t{");
    
                foreach (var parameterProperty in parameters)
    
                    WriteProperty(sb, domainDirectoryInfo.Name, className, parameterProperty);
    
                }
                sb.AppendLine("\t}");
                sb.AppendLine("}");
                WriteToFile(domainDirectoryInfo, className, sb.ToString());
            }
    
    
            private static void WriteType(DirectoryInfo domainDirectoryInfo, string ns, Type type)
    
                if (type.Enum.Any()) WriteTypeEnum(domainDirectoryInfo, ns, type);
    
                /*if (type.Properties.Any())*/
                WriteTypeClass(domainDirectoryInfo, ns, type);
    
                WriteTypeSimple(domainDirectoryInfo, type);
            }
    
    
            private static void WriteTypeSimple(DirectoryInfo domainDirectoryInfo, Type type)
    
            private static void WriteTypeClass(DirectoryInfo domainDirectoryInfo, string ns, Type type)
    
                if ("object" != type.Kind) return;
                var className = type.Name;
    
                var sb = new StringBuilder();
    
                sb.AppendFormat("using MasterDevs.ChromeDevTools;");
    
                sb.AppendLine("using Newtonsoft.Json;");
                sb.AppendLine("using System.Collections.Generic;");
                sb.AppendLine();
    
                sb.AppendFormat("namespace {0}.{1}.{2}", RootNamespace, ns, domainDirectoryInfo.Name);
    
                sb.AppendLine();
                sb.AppendLine("{");
                sb.AppendLine("\t/// <summary>");
    
                sb.AppendFormat("\t/// {0}", type.Description);
    
                sb.AppendLine();
                sb.AppendLine("\t/// </summary>");
    
                WriteSupportedBy(sb, type);
    
                sb.AppendFormat("\tpublic class {0}", className);
                sb.AppendLine();
                sb.AppendLine("\t{");
    
                foreach (var propertyDescription in type.Properties)
    
                    WriteProperty(sb, domainDirectoryInfo.Name, className, propertyDescription);
    
                }
                sb.AppendLine("\t}");
                sb.AppendLine("}");
                WriteToFile(domainDirectoryInfo, className, sb.ToString());
            }
    
    
            private static void WriteProperty(StringBuilder sb, string domain, string className, Property property)
    
                var propertyName = GeneratePropertyName(property.Name);
                string propertyType = property.Kind;
                if (null != property.TypeReference)
    
                    propertyType = GeneratePropertyTypeFromReference(domain, property.TypeReference);
    
    brewdente's avatar
    brewdente committed
                else if ("array" == propertyType)
    
                    var arrayDescription = property.Items;
                    if (null != arrayDescription.TypeReference)
    
                        propertyType = GeneratePropertyTypeFromReference(domain, arrayDescription.TypeReference) + "[]";
    
    brewdente's avatar
    brewdente committed
                    }
    
    brewdente's avatar
    brewdente committed
                        if ("object" == arrayType)
    
                        {
                            var internalClassName = ToCamelCase(propertyName) + "Array";
                            propertyType = internalClassName + "[]";
                            sb.AppendFormat("\t\tpublic class {0}", internalClassName);
                            sb.AppendLine();
                            sb.AppendLine("\t\t{");
    
                            foreach (var internalProperty in arrayDescription.Properties)
    
                                WriteProperty(sb, domain, internalClassName, internalProperty);
    
                            }
                            sb.AppendLine("\t\t}");
                            sb.AppendLine();
    
    brewdente's avatar
    brewdente committed
                        }
                        else
    
                            propertyType = GeneratePropertyType(arrayDescription.Kind) + "[]";
    
    brewdente's avatar
    brewdente committed
                }
    
                else
                {
                    propertyType = GeneratePropertyType(propertyType.ToString());
    
    brewdente's avatar
    brewdente committed
                }
    
    
                string[] referenceTypes = new string[] { "long", "bool" };
    
                // If the property is optional, but a value type in .NET, make it nullable,
                // so that the property becomes optional.
                if (property.Optional && referenceTypes.Contains(propertyType))
                {
                    propertyType += "?";
                }
    
    
                sb.AppendLine("\t\t/// <summary>");
    
                sb.AppendFormat("\t\t/// Gets or sets {0}", property.Description ?? propertyName);
    
                sb.AppendLine();
                sb.AppendLine("\t\t/// </summary>");
    
    brewdente's avatar
    brewdente committed
                if (className == propertyName)
    
                    sb.AppendFormat("\t\t[JsonProperty(\"{0}\")]", property.Name);
    
                    sb.AppendLine();
                    propertyName += "Child";
                }
    
                else if (property.Optional)
                {
                    sb.AppendLine("\t\t[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]");
                }
    
                sb.AppendFormat("\t\tpublic {0} {1} {{ get; set; }}", propertyType, propertyName);
                sb.AppendLine();
            }
    
            private static string GeneratePropertyTypeFromReference(string domain, string propertyRef)
            {
                if (null == propertyRef) return null;
                var propertyPaths = propertyRef.Split('.');
                if (1 == propertyPaths.Length)
                {
                    Dictionary<string, string> domainDictionary;
                    string inDomainType;
    
    brewdente's avatar
    brewdente committed
                    if (_DomainPropertyTypes.TryGetValue(domain, out domainDictionary)
    
                        && domainDictionary.TryGetValue(propertyPaths[0], out inDomainType))
                    {
    
    brewdente's avatar
    brewdente committed
                        if (inDomainType.StartsWith(domain + "."))
    
                        {
                            return inDomainType.Substring(inDomainType.IndexOf('.') + 1);
                        }
                        return inDomainType;
                    }
                    return propertyPaths[0];
                }
                else
                {
                    domain = propertyPaths[0];
                    var name = propertyPaths[1];
                    return _DomainPropertyTypes[domain][name];
                }
            }
    
            private static string GeneratePropertyType(string propertyType)
            {
    
    brewdente's avatar
    brewdente committed
                switch (propertyType)
    
                {
                    case "number": return "double";
                    case "integer": return "long";
                    case "boolean": return "bool";
                    case "any": return "object";
                    default: return propertyType;
                }
            }
    
    
            private static bool IsGeneratedNativeType(string propertyType)
            {
                switch (propertyType)
                {
                    case "double":
                    case "long":
                    case "bool":
                    case "object":
                        return true;
                    default:
                        return false;
                }
            }
    
    
            private static string GeneratePropertyName(string propertyName)
            {
                return ToCamelCase(propertyName);
            }
    
            private static string ToCamelCase(string propertyName)
            {
                return Char.ToUpper(propertyName[0]).ToString() + propertyName.Substring(1);
            }
    
    
            private static void WriteTypeEnum(DirectoryInfo domainDirectoryInfo, string ns, Type type)
    
                StringBuilder sb = new StringBuilder();
    
                sb.AppendLine("using MasterDevs.ChromeDevTools;");
                sb.AppendLine("using Newtonsoft.Json;");
                sb.AppendLine("using Newtonsoft.Json.Converters;");
                sb.AppendLine("using System.Runtime.Serialization;");
    
                sb.AppendLine();
                sb.AppendLine();
    
                sb.AppendFormat("namespace {0}.{1}.{2}", RootNamespace, ns, domainDirectoryInfo.Name);
    
                sb.AppendLine("{");
                sb.AppendLine("\t/// <summary>");
    
                sb.AppendFormat("\t/// {0}", type.Description);
    
                sb.AppendLine();
                sb.AppendLine("\t/// </summary>");
    
                sb.AppendLine("\t[JsonConverter(typeof(StringEnumConverter))]");
    
                sb.AppendFormat("\tpublic enum {0}", enumName);
                sb.AppendLine();
                sb.AppendLine("\t{");
    
                    if (enumValueName.Contains("-"))
                    {
                        sb.AppendFormat("\t\t\t[EnumMember(Value = \"{0}\")]", enumValueName);
                        sb.AppendLine();
                    }
    
    
                    sb.AppendFormat("\t\t\t{0},", ToCamelCase(enumValueName.Replace("-", "_")));
    
                    sb.AppendLine();
                }
                sb.AppendLine("\t}");
                sb.AppendLine("}");
                WriteToFile(domainDirectoryInfo, enumName, sb.ToString());
            }
    
    
            private static void WriteSupportedBy(StringBuilder sb, ProtocolItem type)
            {
                WriteSupportedBy(sb, type.SupportedBy);
            }
    
            private static void WriteSupportedBy(StringBuilder sb, IEnumerable<string> supportedBy)
            {
    
                    sb.AppendLine($"\t[SupportedBy(\"{browser}\")]");
    
            private static void WriteToFile(DirectoryInfo domainDirectoryInfo, string fileName, string fileContents)
            {
                var fullPath = Path.Combine(domainDirectoryInfo.FullName, fileName + ".cs");
                if (File.Exists(fullPath)) File.Delete(fullPath);
                File.WriteAllText(fullPath, fileContents);
            }
    
            private static DirectoryInfo CreateDomainFolder(DirectoryInfo parentDirectory, string domainName)
            {
    
    brewdente's avatar
    brewdente committed
                return parentDirectory.CreateSubdirectory(domainName);
    
    brewdente's avatar
    brewdente committed
    }