From 8bf1dbfc6ed4e3b07d39a0fdbc12fd972053b170 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Mon, 15 Aug 2022 02:28:38 -0300 Subject: [PATCH 01/13] init cli tool --- EdgeDB.Net.sln | 7 +++++++ src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj | 14 ++++++++++++++ src/EdgeDB.Net.CLI/ICommand.cs | 6 ++++++ src/EdgeDB.Net.CLI/Program.cs | 15 +++++++++++++++ 4 files changed, 42 insertions(+) create mode 100644 src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj create mode 100644 src/EdgeDB.Net.CLI/ICommand.cs create mode 100644 src/EdgeDB.Net.CLI/Program.cs diff --git a/EdgeDB.Net.sln b/EdgeDB.Net.sln index 5e490f2b..3206c7f4 100644 --- a/EdgeDB.Net.sln +++ b/EdgeDB.Net.sln @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Serializer.Experimen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Examples.ExampleTODOApi", "examples\EdgeDB.Examples.ExampleTODOApi\EdgeDB.Examples.ExampleTODOApi.csproj", "{E38429C6-53A5-4311-8189-1F78238666DC}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdgeDB.Net.CLI", "src\EdgeDB.Net.CLI\EdgeDB.Net.CLI.csproj", "{77D1980E-9835-4D16-B7B4-6CB5A4BD570C}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -85,6 +87,10 @@ Global {E38429C6-53A5-4311-8189-1F78238666DC}.Debug|Any CPU.Build.0 = Debug|Any CPU {E38429C6-53A5-4311-8189-1F78238666DC}.Release|Any CPU.ActiveCfg = Release|Any CPU {E38429C6-53A5-4311-8189-1F78238666DC}.Release|Any CPU.Build.0 = Release|Any CPU + {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -101,6 +107,7 @@ Global {3A4AAAA0-9948-43D3-B838-8EFAC130240C} = {67ED9EF0-7828-44C0-8CB0-DEBD69EC94CA} {6FA68DEA-D398-4A5B-8025-5F15C728F04C} = {49B6FB80-A675-4ECA-802C-2337A4F37566} {E38429C6-53A5-4311-8189-1F78238666DC} = {6FC214F5-C912-4D99-91B1-3E9F52A4E11B} + {77D1980E-9835-4D16-B7B4-6CB5A4BD570C} = {025AAADF-16AF-4367-9C3D-9E60EDED832F} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4E90C94F-D693-4411-82F3-2051DE1BE052} diff --git a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj new file mode 100644 index 00000000..d69c7d14 --- /dev/null +++ b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj @@ -0,0 +1,14 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + diff --git a/src/EdgeDB.Net.CLI/ICommand.cs b/src/EdgeDB.Net.CLI/ICommand.cs new file mode 100644 index 00000000..a9deebb7 --- /dev/null +++ b/src/EdgeDB.Net.CLI/ICommand.cs @@ -0,0 +1,6 @@ +namespace EdgeDB.CLI; + +interface ICommand +{ + Task ExecuteAsync(); +} \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Program.cs b/src/EdgeDB.Net.CLI/Program.cs new file mode 100644 index 00000000..8afe27e3 --- /dev/null +++ b/src/EdgeDB.Net.CLI/Program.cs @@ -0,0 +1,15 @@ +// load commands +using CommandLine; +using EdgeDB.CLI; + +var commands = typeof(Program).Assembly.GetTypes().Where(x => x.GetInterfaces().Any(x => x == typeof(ICommand))); + +await Parser.Default.ParseArguments(args, commands.ToArray()) + .WithNotParsed(HandleNoCommand) + .WithParsedAsync(x => x.ExecuteAsync); + + +void HandleNoCommand(IEnumerable errors) +{ + +} From 4e89afad5453de143ae49708ea2982832ff395b2 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 21 Aug 2022 05:40:34 -0300 Subject: [PATCH 02/13] generate command, connection parameters & parsing --- EdgeDB.Net.sln | 9 +- .../Arguments/ConnectionArguments.cs | 94 +++++ src/EdgeDB.Net.CLI/Commands/Generate.cs | 117 ++++++ src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj | 5 + src/EdgeDB.Net.CLI/ErrorHandler.cs | 36 ++ .../FrameworkWriters/CSharpLanguageWriter.cs | 8 +- .../FrameworkWriters/ILanguageWriter.cs | 18 + src/EdgeDB.Net.CLI/Program.cs | 23 +- .../Properties/launchSettings.json | 8 + .../EdgeDB.Net.CLI/Utils}/CodeWriter.cs | 11 +- src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs | 30 ++ src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs | 49 +++ src/EdgeDB.Net.Driver/AssemblyInfo.cs | 1 + .../Clients/EdgeDBBinaryClient.cs | 161 +++++---- src/EdgeDB.Net.Driver/Codecs/Object.cs | 20 +- test.edgeql | 3 + tools/EdgeDB.DotnetTool/Commands/Generate.cs | 117 ------ .../EdgeDB.DotnetTool.csproj | 30 -- tools/EdgeDB.DotnetTool/Lexer/SchemaBuffer.cs | 108 ------ tools/EdgeDB.DotnetTool/Lexer/SchemaLexer.cs | 252 ------------- tools/EdgeDB.DotnetTool/Lexer/Token.cs | 23 -- tools/EdgeDB.DotnetTool/Lexer/TokenType.cs | 38 -- tools/EdgeDB.DotnetTool/Program.cs | 7 - .../Properties/launchSettings.json | 11 - .../EdgeDB.DotnetTool/Schemas/ClassBuilder.cs | 225 ------------ .../Schemas/ClassBuilderContext.cs | 27 -- .../Schemas/Models/Annotation.cs | 17 - .../Schemas/Models/Constraint.cs | 15 - .../Schemas/Models/Module.cs | 15 - .../Schemas/Models/Property.cs | 46 --- .../EdgeDB.DotnetTool/Schemas/Models/Type.cs | 28 -- .../EdgeDB.DotnetTool/Schemas/SchemaReader.cs | 333 ------------------ tools/EdgeDB.DotnetTool/Util/PascalUtils.cs | 45 --- tools/EdgeDB.DotnetTool/dbschema/default.esdl | 141 -------- .../dbschema/migrations/00001.edgeql | 117 ------ tools/EdgeDB.DotnetTool/edgedb.toml | 2 - tools/EdgeDB.DotnetTool/test.esdl | 113 ------ 37 files changed, 487 insertions(+), 1816 deletions(-) create mode 100644 src/EdgeDB.Net.CLI/Arguments/ConnectionArguments.cs create mode 100644 src/EdgeDB.Net.CLI/Commands/Generate.cs create mode 100644 src/EdgeDB.Net.CLI/ErrorHandler.cs rename tools/EdgeDB.DotnetTool/ICommand.cs => src/EdgeDB.Net.CLI/FrameworkWriters/CSharpLanguageWriter.cs (56%) create mode 100644 src/EdgeDB.Net.CLI/FrameworkWriters/ILanguageWriter.cs create mode 100644 src/EdgeDB.Net.CLI/Properties/launchSettings.json rename {tools/EdgeDB.DotnetTool/Util => src/EdgeDB.Net.CLI/Utils}/CodeWriter.cs (91%) create mode 100644 src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs create mode 100644 src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs create mode 100644 test.edgeql delete mode 100644 tools/EdgeDB.DotnetTool/Commands/Generate.cs delete mode 100644 tools/EdgeDB.DotnetTool/EdgeDB.DotnetTool.csproj delete mode 100644 tools/EdgeDB.DotnetTool/Lexer/SchemaBuffer.cs delete mode 100644 tools/EdgeDB.DotnetTool/Lexer/SchemaLexer.cs delete mode 100644 tools/EdgeDB.DotnetTool/Lexer/Token.cs delete mode 100644 tools/EdgeDB.DotnetTool/Lexer/TokenType.cs delete mode 100644 tools/EdgeDB.DotnetTool/Program.cs delete mode 100644 tools/EdgeDB.DotnetTool/Properties/launchSettings.json delete mode 100644 tools/EdgeDB.DotnetTool/Schemas/ClassBuilder.cs delete mode 100644 tools/EdgeDB.DotnetTool/Schemas/ClassBuilderContext.cs delete mode 100644 tools/EdgeDB.DotnetTool/Schemas/Models/Annotation.cs delete mode 100644 tools/EdgeDB.DotnetTool/Schemas/Models/Constraint.cs delete mode 100644 tools/EdgeDB.DotnetTool/Schemas/Models/Module.cs delete mode 100644 tools/EdgeDB.DotnetTool/Schemas/Models/Property.cs delete mode 100644 tools/EdgeDB.DotnetTool/Schemas/Models/Type.cs delete mode 100644 tools/EdgeDB.DotnetTool/Schemas/SchemaReader.cs delete mode 100644 tools/EdgeDB.DotnetTool/Util/PascalUtils.cs delete mode 100644 tools/EdgeDB.DotnetTool/dbschema/default.esdl delete mode 100644 tools/EdgeDB.DotnetTool/dbschema/migrations/00001.edgeql delete mode 100644 tools/EdgeDB.DotnetTool/edgedb.toml delete mode 100644 tools/EdgeDB.DotnetTool/test.esdl diff --git a/EdgeDB.Net.sln b/EdgeDB.Net.sln index 3206c7f4..85aa8681 100644 --- a/EdgeDB.Net.sln +++ b/EdgeDB.Net.sln @@ -21,8 +21,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tools", "Tools", "{67ED9EF0 EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.QueryBuilder.OperatorGenerator", "tools\EdgeDB.QueryBuilder.OperatorGenerator\EdgeDB.QueryBuilder.OperatorGenerator.csproj", "{1557B745-EB4F-449A-9BE7-180C8990AD47}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.DotnetTool", "tools\EdgeDB.DotnetTool\EdgeDB.DotnetTool.csproj", "{74DB9D5E-9BA9-4282-90EA-2F7BDC4C4FBD}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Tests.Integration", "tests\EdgeDB.Tests.Integration\EdgeDB.Tests.Integration.csproj", "{C189294A-4990-4A06-B120-A0AF03A798C6}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Tests.Benchmarks", "tests\EdgeDB.Tests.Benchmarks\EdgeDB.Tests.Benchmarks.csproj", "{5FFD1E88-614D-409B-8420-F9571AC7CA60}" @@ -35,7 +33,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Serializer.Experimen EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Examples.ExampleTODOApi", "examples\EdgeDB.Examples.ExampleTODOApi\EdgeDB.Examples.ExampleTODOApi.csproj", "{E38429C6-53A5-4311-8189-1F78238666DC}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdgeDB.Net.CLI", "src\EdgeDB.Net.CLI\EdgeDB.Net.CLI.csproj", "{77D1980E-9835-4D16-B7B4-6CB5A4BD570C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Net.CLI", "src\EdgeDB.Net.CLI\EdgeDB.Net.CLI.csproj", "{77D1980E-9835-4D16-B7B4-6CB5A4BD570C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -63,10 +61,6 @@ Global {1557B745-EB4F-449A-9BE7-180C8990AD47}.Debug|Any CPU.Build.0 = Debug|Any CPU {1557B745-EB4F-449A-9BE7-180C8990AD47}.Release|Any CPU.ActiveCfg = Release|Any CPU {1557B745-EB4F-449A-9BE7-180C8990AD47}.Release|Any CPU.Build.0 = Release|Any CPU - {74DB9D5E-9BA9-4282-90EA-2F7BDC4C4FBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {74DB9D5E-9BA9-4282-90EA-2F7BDC4C4FBD}.Debug|Any CPU.Build.0 = Debug|Any CPU - {74DB9D5E-9BA9-4282-90EA-2F7BDC4C4FBD}.Release|Any CPU.ActiveCfg = Release|Any CPU - {74DB9D5E-9BA9-4282-90EA-2F7BDC4C4FBD}.Release|Any CPU.Build.0 = Release|Any CPU {C189294A-4990-4A06-B120-A0AF03A798C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {C189294A-4990-4A06-B120-A0AF03A798C6}.Debug|Any CPU.Build.0 = Debug|Any CPU {C189294A-4990-4A06-B120-A0AF03A798C6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -101,7 +95,6 @@ Global {CD4A4B5D-4B67-4E64-A13B-3F7BF770B056} = {E6B9FABC-241B-4561-9A94-E67B6BE380E2} {4CC2101C-D0AB-4F08-A0FD-205F96958196} = {025AAADF-16AF-4367-9C3D-9E60EDED832F} {1557B745-EB4F-449A-9BE7-180C8990AD47} = {67ED9EF0-7828-44C0-8CB0-DEBD69EC94CA} - {74DB9D5E-9BA9-4282-90EA-2F7BDC4C4FBD} = {67ED9EF0-7828-44C0-8CB0-DEBD69EC94CA} {C189294A-4990-4A06-B120-A0AF03A798C6} = {E6B9FABC-241B-4561-9A94-E67B6BE380E2} {5FFD1E88-614D-409B-8420-F9571AC7CA60} = {E6B9FABC-241B-4561-9A94-E67B6BE380E2} {3A4AAAA0-9948-43D3-B838-8EFAC130240C} = {67ED9EF0-7828-44C0-8CB0-DEBD69EC94CA} diff --git a/src/EdgeDB.Net.CLI/Arguments/ConnectionArguments.cs b/src/EdgeDB.Net.CLI/Arguments/ConnectionArguments.cs new file mode 100644 index 00000000..890af00f --- /dev/null +++ b/src/EdgeDB.Net.CLI/Arguments/ConnectionArguments.cs @@ -0,0 +1,94 @@ +using CommandLine; +using EdgeDB.CLI.Utils; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI.Arguments +{ + public class ConnectionArguments + { + [Option("dsn", HelpText = "DSN for EdgeDB to connect to (overrides all other options except password)")] + public string? DSN { get; set; } + + [Option("credentials-file", HelpText = "Path to JSON file to read credentials from")] + public string? CredentialsFile { get; set; } + + [Option('I', "instance", HelpText = "Local instance name created with edgedb instance create to connect to (overrides host and port)")] + public string? Instance { get; set; } + + [Option('H', "host", HelpText = "Host of the EdgeDB instance")] + public string? Host { get; set; } + + [Option('P', "port", HelpText = "Port to connect to EdgeDB")] + public int? Port { get; set; } + + [Option('d', "database", HelpText = "Database name to connect to")] + public string? Database { get; set; } + + [Option('u', "user", HelpText = "User name of the EdgeDB user")] + public string? User { get; set; } + + [Option("password", HelpText = "Ask for password on the terminal (TTY)")] + public bool Password { get; set; } + + [Option("password-from-stdin", HelpText = "Read the password from stdin rather than TTY (useful for scripts)")] + public bool PasswordFromSTDIN { get; set; } + + [Option("tls-ca-file", HelpText = "Certificate to match server against\n\nThis might either be full self-signed server certificate or certificate authority (CA) certificate that server certificate is signed with.")] + public string? TLSCAFile { get; set; } + + [Option("tls-security", HelpText = "Specify the client-side TLS security mode.")] + public TLSSecurityMode? TLSSecurity { get; set; } + + public EdgeDBConnection GetConnection() + { + if (DSN is not null) + return EdgeDBConnection.FromDSN(DSN); + + if (Instance is not null) + return EdgeDBConnection.FromInstanceName(Instance); + + if (CredentialsFile is not null) + return JsonConvert.DeserializeObject(File.ReadAllText(CredentialsFile)) + ?? throw new NullReferenceException($"The file '{CredentialsFile}' didn't contain a valid credential definition"); + + // create the resolved connection + var resolved = EdgeDBConnection.ResolveConnection(); + + if (Host is not null) + resolved.Hostname = Host; + + if (Port.HasValue) + resolved.Port = Port.Value; + + if (Database is not null) + resolved.Database = Database; + + if (User is not null) + resolved.Username = User; + + if (Password) + { + // read password from console + Console.Write($"Password for '{resolved.Database}': "); + + resolved.Password = ConsoleUtils.ReadSecretInput(); + } + + if (PasswordFromSTDIN) + resolved.Password = Console.ReadLine(); + + if (TLSCAFile is not null) + resolved.TLSCertificateAuthority = TLSCAFile; + + if (TLSSecurity.HasValue) + resolved.TLSSecurity = TLSSecurity.Value; + + return resolved; + } + } +} diff --git a/src/EdgeDB.Net.CLI/Commands/Generate.cs b/src/EdgeDB.Net.CLI/Commands/Generate.cs new file mode 100644 index 00000000..90f218dc --- /dev/null +++ b/src/EdgeDB.Net.CLI/Commands/Generate.cs @@ -0,0 +1,117 @@ +using CommandLine; +using EdgeDB.CLI.Arguments; +using EdgeDB.CLI.Utils; +using EdgeDB.Codecs; +using System.Reflection; + +namespace EdgeDB.CLI; + +[Verb("generate", HelpText = "Generate or updates csharp classes from .edgeql files.")] +public class Generate : ConnectionArguments, ICommand +{ + [Option('p', "build-project", HelpText = "Whether or not to create the default class library that will contain the generated source code.")] + public bool GenerateProject { get; set; } = true; + + [Option('o', "output", HelpText = "The output directory for the generated source to be placed.")] + public string? OutputDirectory { get; set; } + + [Option('n', "generated-project-name", HelpText = "The name of the generated project.")] + public string GeneratedProjectName { get; set; } = "EdgeDB.Generated"; + + [Option("target", HelpText = "The language the generated code should target\n\nValid inputs are: C#, F#, and VB")] + public string? TargetLanguage { get; set; } + + public async Task ExecuteAsync() + { + TargetLanguage = (TargetLanguage ?? "c#").ToLower(); + + // get connection info + var connection = GetConnection(); + + // create the client + var client = new EdgeDBTcpClient(connection, new()); + + Console.WriteLine($"Connecting to {connection.Hostname}:{connection.Port}..."); + await client.ConnectAsync(); + + var projectRoot = ProjectUtils.GetProjectRoot(); + + OutputDirectory ??= Path.Combine(projectRoot, "EdgeDB.Generated"); + + if(GenerateProject && !Directory.Exists(OutputDirectory)) + { + Console.WriteLine("Creating EdgeDB.Generated project..."); + await ProjectUtils.CreateGeneratedProjectAsync(projectRoot, GeneratedProjectName, TargetLanguage); + } + + // find edgeql files + var edgeqlFiles = ProjectUtils.GetTargetEdgeQLFiles(projectRoot); + + Console.WriteLine($"Generating {edgeqlFiles.Count()} files..."); + + var queriesExtensionWriter = CreateDefaultReader(); + + var languageWriter = ILanguageWriter.GetWriter(TargetLanguage); + + foreach (var file in edgeqlFiles) + { + var edgeql = File.ReadAllText(file); + var parseResult = await client.ParseAsync(edgeql, Cardinality.Many, IOFormat.Binary, Capabilities.All, default); + + ProcessEdgeQLFile(parseResult, languageWriter, queriesExtensionWriter); + } + } + + private void ProcessEdgeQLFile(EdgeDBBinaryClient.ParseResult parseResult, ILanguageWriter writer, + CodeWriter extWriter) + { + // generate the type + var codecType = GetTypeOfCodec(parseResult.OutCodec.Codec); + } + + private CodecTypeInfo GetTypeOfCodec(ICodec codec, string? name = null) + { + CodecTypeInfo typeInfo; + + // TODO: complete codec parsing + + typeInfo = codec switch + { + Codecs.Object obj => new CodecTypeInfo + { + IsObject = true, + Children = obj.InnerCodecs.Select((x, i) => GetTypeOfCodec(x, obj.PropertyNames[i])), + TypeName = name + }, + ICodec set when ReflectionUtils.IsSubclassOfRawGeneric(set.GetType(), typeof(Set<>)) => new CodecTypeInfo + { + + Children = new[] { GetTypeOfCodec((ICodec)set.GetType().GetField("_innerCodec", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(set)!) } + } + }; + + typeInfo.Name = name ?? typeInfo.Name; + } + + private class CodecTypeInfo + { + public bool IsArray { get; set; } + public bool IsObject { get; set; } + public bool IsTuple { get; set; } + public string? Name { get; set; } + public string? TypeName { get; set; } + public IEnumerable? Children { get; set; } + } + + private CodeWriter CreateDefaultReader() + { + var writer = new CodeWriter(); + writer.AppendLine("// AUTOGENERATED: DO NOT MODIFY"); + writer.AppendLine($"// Generated on {DateTime.UtcNow:O}"); + writer.AppendLine($"using EdgeDB;"); + writer.AppendLine($"using {GeneratedProjectName};"); + writer.AppendLine(); + + return writer; + } +} diff --git a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj index d69c7d14..38790c03 100644 --- a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj +++ b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj @@ -8,7 +8,12 @@ + + + + + diff --git a/src/EdgeDB.Net.CLI/ErrorHandler.cs b/src/EdgeDB.Net.CLI/ErrorHandler.cs new file mode 100644 index 00000000..15b648a8 --- /dev/null +++ b/src/EdgeDB.Net.CLI/ErrorHandler.cs @@ -0,0 +1,36 @@ +using CommandLine; +using CommandLine.Text; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI +{ + internal class ErrorHandler + { + public static void HandleErrors(ParserResult result, IEnumerable errors) + { + foreach(var error in errors) + { + switch (error) + { + case NoVerbSelectedError noVerbs: + { + Console.WriteLine("No command specified use --help to view a list of commands"); + } + break; + default: + HelpText.AutoBuild(result, h => + { + h.Heading = "EdgeDB.Net CLI"; + h.Copyright = "EdgeDB (c) 2022 edgedb.com"; + return h; + }); + break; + } + } + } + } +} diff --git a/tools/EdgeDB.DotnetTool/ICommand.cs b/src/EdgeDB.Net.CLI/FrameworkWriters/CSharpLanguageWriter.cs similarity index 56% rename from tools/EdgeDB.DotnetTool/ICommand.cs rename to src/EdgeDB.Net.CLI/FrameworkWriters/CSharpLanguageWriter.cs index 909ee4c7..d7f2dcee 100644 --- a/tools/EdgeDB.DotnetTool/ICommand.cs +++ b/src/EdgeDB.Net.CLI/FrameworkWriters/CSharpLanguageWriter.cs @@ -4,10 +4,8 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.DotnetTool +namespace EdgeDB.CLI; + +internal class CSharpLanguageWriter : ILanguageWriter { - internal interface ICommand - { - void Execute(); - } } diff --git a/src/EdgeDB.Net.CLI/FrameworkWriters/ILanguageWriter.cs b/src/EdgeDB.Net.CLI/FrameworkWriters/ILanguageWriter.cs new file mode 100644 index 00000000..3d6a872b --- /dev/null +++ b/src/EdgeDB.Net.CLI/FrameworkWriters/ILanguageWriter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI; + +internal interface ILanguageWriter +{ + public static ILanguageWriter GetWriter(string lang) + { + return lang switch + { + "c#" => new CSharpLanguageWriter() + }; + } +} diff --git a/src/EdgeDB.Net.CLI/Program.cs b/src/EdgeDB.Net.CLI/Program.cs index 8afe27e3..39284564 100644 --- a/src/EdgeDB.Net.CLI/Program.cs +++ b/src/EdgeDB.Net.CLI/Program.cs @@ -1,15 +1,26 @@ // load commands using CommandLine; +using CommandLine.Text; using EdgeDB.CLI; var commands = typeof(Program).Assembly.GetTypes().Where(x => x.GetInterfaces().Any(x => x == typeof(ICommand))); -await Parser.Default.ParseArguments(args, commands.ToArray()) - .WithNotParsed(HandleNoCommand) - .WithParsedAsync(x => x.ExecuteAsync); +var parser = new Parser(x => +{ + x.HelpWriter = null; +}); + +var result = parser.ParseArguments(args, commands.ToArray()); +var commandResult = await result.WithParsedAsync(x => x.ExecuteAsync()); -void HandleNoCommand(IEnumerable errors) +var helpText = HelpText.AutoBuild(commandResult, h => { - -} + h.AdditionalNewLineAfterOption = true; + h.Heading = "EdgeDB.Net CLI"; + h.Copyright = "Copyright (c) 2022 EdgeDB"; + + return h; +}, e => e, verbsIndex: true); + +Console.WriteLine(helpText); diff --git a/src/EdgeDB.Net.CLI/Properties/launchSettings.json b/src/EdgeDB.Net.CLI/Properties/launchSettings.json new file mode 100644 index 00000000..08a62d0f --- /dev/null +++ b/src/EdgeDB.Net.CLI/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "EdgeDB.Net.CLI": { + "commandName": "Project", + "commandLineArgs": "generate" + } + } +} \ No newline at end of file diff --git a/tools/EdgeDB.DotnetTool/Util/CodeWriter.cs b/src/EdgeDB.Net.CLI/Utils/CodeWriter.cs similarity index 91% rename from tools/EdgeDB.DotnetTool/Util/CodeWriter.cs rename to src/EdgeDB.Net.CLI/Utils/CodeWriter.cs index 4c341d8d..e66a42ef 100644 --- a/tools/EdgeDB.DotnetTool/Util/CodeWriter.cs +++ b/src/EdgeDB.Net.CLI/Utils/CodeWriter.cs @@ -1,17 +1,14 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text; -using System.Threading.Tasks; -namespace EdgeDB.DotnetTool +namespace EdgeDB.CLI { internal class CodeWriter { public readonly StringBuilder Content = new(); - public int IndentLevel { get; private set; } - + private readonly ScopeTracker _scopeTracker; //We only need one. It can be reused. public CodeWriter() @@ -41,7 +38,8 @@ public IDisposable BeginScope() return _scopeTracker; } - public void EndLine() => Content.AppendLine(); + public void EndLine() + => Content.AppendLine(); public void EndScope() { @@ -61,7 +59,6 @@ public ScopeTracker(CodeWriter parent) { Parent = parent; } - public CodeWriter Parent { get; } public void Dispose() diff --git a/src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs b/src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs new file mode 100644 index 00000000..18bb2f70 --- /dev/null +++ b/src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI.Utils +{ + internal class ConsoleUtils + { + public static string ReadSecretInput() + { + string input = ""; + ConsoleKey key; + do + { + var keyInfo = Console.ReadKey(true); + key = keyInfo.Key; + + if (key == ConsoleKey.Backspace) + input = input.Length > 0 ? input[..^1] : ""; + else if(!char.IsControl(keyInfo.KeyChar)) + input += keyInfo.KeyChar; + } + while (key != ConsoleKey.Enter); + + return input; + } + } +} diff --git a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs new file mode 100644 index 00000000..57cb650d --- /dev/null +++ b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs @@ -0,0 +1,49 @@ +using CliWrap; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI.Utils +{ + internal class ProjectUtils + { + public static string GetProjectRoot() + { + var directory = Environment.CurrentDirectory; + bool foundRoot = false; + + while (!foundRoot) + { + if ( + !(foundRoot = Directory.GetFiles(directory, "*", SearchOption.TopDirectoryOnly).Any(x => x.EndsWith($"{Path.DirectorySeparatorChar}edgedb.toml"))) && + (directory = Directory.GetParent(directory!)?.FullName) is null) + throw new FileNotFoundException("Could not find edgedb.toml in the current and parent directories"); + } + + return directory; + } + + public static async Task CreateGeneratedProjectAsync(string root, string name, string language) + { + var result = await Cli.Wrap("dotnet") + .WithArguments($"new classlib --framework \"net6.0\" -o {name} -lang {language}") + .WithWorkingDirectory(root) + .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) + .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) + .ExecuteAsync(); + + // remove default file + File.Delete(Path.Combine(root, name, language switch + { + "c#" => "Class1.cs", + "vb" => "Class1.vb", + "f#" => "Library.fs", + })); + } + + public static IEnumerable GetTargetEdgeQLFiles(string root) + => Directory.GetFiles(root, "*.edgeql", SearchOption.AllDirectories).Where(x => !x.StartsWith(Path.Combine(root, "dbschema", "migrations"))); + } +} diff --git a/src/EdgeDB.Net.Driver/AssemblyInfo.cs b/src/EdgeDB.Net.Driver/AssemblyInfo.cs index 2c4eae45..c2b2a32a 100644 --- a/src/EdgeDB.Net.Driver/AssemblyInfo.cs +++ b/src/EdgeDB.Net.Driver/AssemblyInfo.cs @@ -1,6 +1,7 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("EdgeDB.Net.QueryBuilder")] +[assembly: InternalsVisibleTo("EdgeDB.Net.CLI")] [assembly: InternalsVisibleTo("EdgeDB.Runtime")] [assembly: InternalsVisibleTo("EdgeDB.ExampleApp")] [assembly: InternalsVisibleTo("EdgeDB.DotnetTool")] diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index 08526489..6720e8de 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -154,6 +154,20 @@ public RawExecuteResult(ICodec codec, List data) } } + internal readonly struct ParseResult + { + public readonly CodecInfo InCodec; + public readonly CodecInfo OutCodec; + public readonly IDictionary State; + + public ParseResult(CodecInfo inCodec, CodecInfo outCodec, IDictionary state) + { + InCodec = inCodec; + OutCodec = outCodec; + State = state; + } + } + /// A general error occored. /// The client received an . /// The client received an unexpected message. @@ -180,73 +194,10 @@ internal async Task ExecuteInternalAsync(string query, IDictio try { - var cacheKey = CodecBuilder.GetCacheHashKey(query, cardinality ?? Cardinality.Many, format); - - var serializedState = Session.Serialize(); - - List p = new(); - - if (!CodecBuilder.TryGetCodecs(cacheKey, out var inCodecInfo, out var outCodecInfo)) - { - bool parseHandlerPredicate(IReceiveable? packet) - { - p.Add(packet); - switch (packet) - { - case ErrorResponse err when err.ErrorCode is not ServerErrorCodes.StateMismatchError: - throw new EdgeDBErrorException(err); - case CommandDataDescription descriptor: - { - outCodecInfo = new(descriptor.OutputTypeDescriptorId, - CodecBuilder.BuildCodec(descriptor.OutputTypeDescriptorId, descriptor.OutputTypeDescriptorBuffer)); - - inCodecInfo = new(descriptor.InputTypeDescriptorId, - CodecBuilder.BuildCodec(descriptor.InputTypeDescriptorId, descriptor.InputTypeDescriptorBuffer)); - - CodecBuilder.UpdateKeyMap(cacheKey, descriptor.InputTypeDescriptorId, descriptor.OutputTypeDescriptorId); - } - break; - case StateDataDescription stateDescriptor: - { - _stateCodec = CodecBuilder.BuildCodec(stateDescriptor.TypeDescriptorId, stateDescriptor.TypeDescriptorBuffer); - _stateDescriptorId = stateDescriptor.TypeDescriptorId; - } - break; - case ReadyForCommand ready: - TransactionState = ready.TransactionState; - return true; - default: - break; - } - - return false; - } - - var stateBuf = _stateCodec?.Serialize(serializedState)!; + var parsed = await ParseAsync(query, cardinality ?? Cardinality.Many, format, capabilities, token).ConfigureAwait(false); - var result = await Duplexer.DuplexAndSyncAsync(new Parse - { - Capabilities = capabilities, - Query = query, - Format = format, - ExpectedCardinality = cardinality ?? Cardinality.Many, - ExplicitObjectIds = _config.ExplicitObjectIds, - StateTypeDescriptorId = _stateDescriptorId, - StateData = stateBuf, - ImplicitLimit = _config.ImplicitLimit, - ImplicitTypeNames = true, // used for type builder - ImplicitTypeIds = true, // used for type builder - }, parseHandlerPredicate, alwaysReturnError: false, token: token).ConfigureAwait(false); - - if (outCodecInfo is null) - throw new MissingCodecException("Couldn't find a valid output codec"); - - if (inCodecInfo is null) - throw new MissingCodecException("Couldn't find a valid input codec"); - } - - if (inCodecInfo.Codec is not IArgumentCodec argumentCodec) - throw new MissingCodecException($"Cannot encode arguments, {inCodecInfo.Codec} is not a registered argument codec"); + if (parsed.InCodec.Codec is not IArgumentCodec argumentCodec) + throw new MissingCodecException($"Cannot encode arguments, {parsed.InCodec.Codec} is not a registered argument codec"); List receivedData = new(); @@ -275,20 +226,20 @@ bool handler(IReceiveable msg) ExpectedCardinality = cardinality ?? Cardinality.Many, ExplicitObjectIds = _config.ExplicitObjectIds, StateTypeDescriptorId = _stateDescriptorId, - StateData = _stateCodec?.Serialize(serializedState), + StateData = _stateCodec?.Serialize(parsed.State), ImplicitTypeNames = true, // used for type builder ImplicitTypeIds = true, // used for type builder Arguments = argumentCodec?.SerializeArguments(args) , ImplicitLimit = _config.ImplicitLimit, - InputTypeDescriptorId = inCodecInfo.Id, - OutputTypeDescriptorId = outCodecInfo.Id, + InputTypeDescriptorId = parsed.InCodec.Id, + OutputTypeDescriptorId = parsed.OutCodec.Id, }, handler, alwaysReturnError: false, token: linkedToken).ConfigureAwait(false); executeResult.ThrowIfErrrorResponse(); execResult = new ExecuteResult(true, null, null, query); - return new RawExecuteResult(outCodecInfo.Codec!, receivedData); + return new RawExecuteResult(parsed.OutCodec.Codec!, receivedData); } catch (OperationCanceledException) { @@ -330,6 +281,76 @@ bool handler(IReceiveable msg) } } + internal async Task ParseAsync(string query, Cardinality cardinality, IOFormat format, Capabilities? capabilities, CancellationToken token) + { + var cacheKey = CodecBuilder.GetCacheHashKey(query, cardinality, format); + + var serializedState = Session.Serialize(); + + List p = new(); + + if (!CodecBuilder.TryGetCodecs(cacheKey, out var inCodecInfo, out var outCodecInfo)) + { + bool parseHandlerPredicate(IReceiveable? packet) + { + p.Add(packet); + switch (packet) + { + case ErrorResponse err when err.ErrorCode is not ServerErrorCodes.StateMismatchError: + throw new EdgeDBErrorException(err); + case CommandDataDescription descriptor: + { + outCodecInfo = new(descriptor.OutputTypeDescriptorId, + CodecBuilder.BuildCodec(descriptor.OutputTypeDescriptorId, descriptor.OutputTypeDescriptorBuffer)); + + inCodecInfo = new(descriptor.InputTypeDescriptorId, + CodecBuilder.BuildCodec(descriptor.InputTypeDescriptorId, descriptor.InputTypeDescriptorBuffer)); + + CodecBuilder.UpdateKeyMap(cacheKey, descriptor.InputTypeDescriptorId, descriptor.OutputTypeDescriptorId); + } + break; + case StateDataDescription stateDescriptor: + { + _stateCodec = CodecBuilder.BuildCodec(stateDescriptor.TypeDescriptorId, stateDescriptor.TypeDescriptorBuffer); + _stateDescriptorId = stateDescriptor.TypeDescriptorId; + } + break; + case ReadyForCommand ready: + TransactionState = ready.TransactionState; + return true; + default: + break; + } + + return false; + } + + var stateBuf = _stateCodec?.Serialize(serializedState)!; + + var result = await Duplexer.DuplexAndSyncAsync(new Parse + { + Capabilities = capabilities, + Query = query, + Format = format, + ExpectedCardinality = cardinality, + ExplicitObjectIds = _config.ExplicitObjectIds, + StateTypeDescriptorId = _stateDescriptorId, + StateData = stateBuf, + ImplicitLimit = _config.ImplicitLimit, + ImplicitTypeNames = true, // used for type builder + ImplicitTypeIds = true, // used for type builder + }, parseHandlerPredicate, alwaysReturnError: false, token: token).ConfigureAwait(false); + + if (outCodecInfo is null) + throw new MissingCodecException("Couldn't find a valid output codec"); + + if (inCodecInfo is null) + throw new MissingCodecException("Couldn't find a valid input codec"); + } + + return new ParseResult(inCodecInfo, outCodecInfo, serializedState); + } + /// /// A general error occored. /// The client received an . diff --git a/src/EdgeDB.Net.Driver/Codecs/Object.cs b/src/EdgeDB.Net.Driver/Codecs/Object.cs index 28b05ec0..6f32d170 100644 --- a/src/EdgeDB.Net.Driver/Codecs/Object.cs +++ b/src/EdgeDB.Net.Driver/Codecs/Object.cs @@ -4,22 +4,22 @@ namespace EdgeDB.Codecs { internal class Object : ICodec, IArgumentCodec { - private readonly ICodec[] _innerCodecs; - private readonly string[] _propertyNames; + internal readonly ICodec[] InnerCodecs; + internal readonly string[] PropertyNames; internal Object(ICodec[] innerCodecs, string[] propertyNames) { - _innerCodecs = innerCodecs; - _propertyNames = propertyNames; + InnerCodecs = innerCodecs; + PropertyNames = propertyNames; } public object? Deserialize(ref PacketReader reader) { var numElements = reader.ReadInt32(); - if (_innerCodecs.Length != numElements) + if (InnerCodecs.Length != numElements) { - throw new ArgumentException($"codecs mismatch for tuple: expected {numElements} codecs, got {_innerCodecs.Length} codecs"); + throw new ArgumentException($"codecs mismatch for tuple: expected {numElements} codecs, got {InnerCodecs.Length} codecs"); } dynamic data = new ExpandoObject(); @@ -29,7 +29,7 @@ internal Object(ICodec[] innerCodecs, string[] propertyNames) { // reserved reader.Skip(4); - var name = _propertyNames[i]; + var name = PropertyNames[i]; var length = reader.ReadInt32(); if(length is -1) @@ -42,7 +42,7 @@ internal Object(ICodec[] innerCodecs, string[] propertyNames) object? value; - value = _innerCodecs[i].Deserialize(innerData); + value = InnerCodecs[i].Deserialize(innerData); dataDictionary.Add(name, value); } @@ -60,7 +60,7 @@ public void SerializeArguments(PacketWriter writer, object? value) object?[]? values = null; if (value is IDictionary dict) - values = _propertyNames.Select(x => dict[x]).ToArray(); + values = PropertyNames.Select(x => dict[x]).ToArray(); else if (value is object?[] arr) value = arr; @@ -73,7 +73,7 @@ public void SerializeArguments(PacketWriter writer, object? value) for (int i = 0; i != values.Length; i++) { var element = values[i]; - var innerCodec = _innerCodecs[i]; + var innerCodec = InnerCodecs[i]; // reserved innerWriter.Write(0); diff --git a/test.edgeql b/test.edgeql new file mode 100644 index 00000000..b5a09212 --- /dev/null +++ b/test.edgeql @@ -0,0 +1,3 @@ +select Person { + name, email +} filter .id = $user_id \ No newline at end of file diff --git a/tools/EdgeDB.DotnetTool/Commands/Generate.cs b/tools/EdgeDB.DotnetTool/Commands/Generate.cs deleted file mode 100644 index 33d161d2..00000000 --- a/tools/EdgeDB.DotnetTool/Commands/Generate.cs +++ /dev/null @@ -1,117 +0,0 @@ -using CommandLine; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using YamlDotNet.Serialization; -using YamlDotNet.Serialization.NamingConventions; - -namespace EdgeDB.DotnetTool.Commands -{ - [Verb("generate", HelpText = "Generates classes based off of a schema")] - internal class Generate : ICommand - { - [Option('c', "dsn", HelpText = "The DSN connection string to connect to the remote instance", Required = false)] - public string? ConnectionString { get; set; } - - [Option('f', "file", Required = false, HelpText = "The file location of the schema")] - public string? FilePath { get; set; } - - [Option('n', "namespace", Required = false, HelpText = "The namespace for the generated code files", Default = "EdgeDB.Generated")] - public string? Namespace { get; set; } - - [Option('o', "output", Required = false, HelpText = "The output directory for the generated files", Default = "./Generated")] - public string? OutputDir { get; set; } - - [Option('v', "verbose", HelpText = "Enables verbose output")] - public bool Verbose { get; set; } - - public void Execute() - { - // use either file or connection - string? schema = null; - - if (FilePath != null) - schema = File.ReadAllText(FilePath!); - else if (ConnectionString != null) - { - Task.Run(() => - { - //var client = new EdgeDBTcpClient(EdgeDBConnection.FromDSN(ConnectionString), new EdgeDBConfig - //{ - // // TODO: config? - //}); - - //await client.ConnectAsync(); - //var result = await client.QuerySingleAsync($"describe schema as sdl"); - return Task.CompletedTask; - - }).GetAwaiter().GetResult(); - } - - if(schema == null) - { - Console.Error.WriteLine("Please either specify the schema file (-n) or a connection string (-c)"); - return; - } - - Console.WriteLine("Generating module..."); - List modules; - var serializer = new SerializerBuilder() - .ConfigureDefaultValuesHandling(DefaultValuesHandling.OmitNull | DefaultValuesHandling.OmitEmptyCollections | DefaultValuesHandling.OmitDefaults) - .WithNamingConvention(UnderscoredNamingConvention.Instance) - .Build(); - - try - { - modules = new SchemaReader(schema).Read(); - Console.WriteLine("Parsed schema!"); - if(Verbose) - Console.WriteLine(serializer.Serialize(modules)); - } - catch(Exception x) - { - Console.Error.WriteLine($"Failed to read schema: {x}"); - return; - } - - try - { - var builder = new ClassBuilder(OutputDir!, Namespace!); - foreach (var module in modules) - { - Console.WriteLine($"Generating {module.Name}..."); - builder.Generate(module, GetValidDotnetName); - } - - } - catch(Exception x) - { - Console.Error.WriteLine($"Failed to build classes from schema: {x}"); - return; - } - - Console.WriteLine("Generation succeeded"); - } - - private static string GetValidDotnetName(string str) - { - Console.WriteLine($"The name \"{str}\" isn't a valid name in DotNet, what would you want to name it instead?"); - Console.Write("> "); - - while (true) - { - var newName = Console.ReadLine(); - - if (newName == null) - continue; - - if (ClassBuilder.IsValidDotnetName(newName)) - { - return newName; - } - } - } - } -} diff --git a/tools/EdgeDB.DotnetTool/EdgeDB.DotnetTool.csproj b/tools/EdgeDB.DotnetTool/EdgeDB.DotnetTool.csproj deleted file mode 100644 index 897e6f52..00000000 --- a/tools/EdgeDB.DotnetTool/EdgeDB.DotnetTool.csproj +++ /dev/null @@ -1,30 +0,0 @@ - - - - Exe - net6.0 - enable - enable - - - - True - 5 - - - - True - 5 - - - - - - - - - - - - - diff --git a/tools/EdgeDB.DotnetTool/Lexer/SchemaBuffer.cs b/tools/EdgeDB.DotnetTool/Lexer/SchemaBuffer.cs deleted file mode 100644 index dc1d8e36..00000000 --- a/tools/EdgeDB.DotnetTool/Lexer/SchemaBuffer.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool.Lexer -{ - internal class SchemaBuffer - { - readonly string _buffer; - int _bufferPos; - - public int Column { get; private set; } - public int Line { get; private set; } - - public SchemaBuffer(string schema) - { - _buffer = schema; - _bufferPos = 0; - Column = 1; - Line = 1; - - } - - public string Peek(int count) - { - string ret = ""; - while (count > 0) - { - ret += GetNextTextElement(ret.Length); - count--; - } - return ret; - } - - public string Read(int count) - { - return MovePosition(Peek(count)); - } - - public string PeekUntil(string str) - { - string ret = ""; - while (!ret.Contains(str)) - { - string temp = GetNextTextElement(ret.Length); - if (temp.Length == 0) - { - break; - } - ret += temp; - } - return ret; - } - - public string ReadUntil(string str) - { - return MovePosition(PeekUntil(str)); - } - - public string ReadWhile(Predicate pred) - { - string ret = ""; - while (true) - { - string temp = GetNextTextElement(ret.Length); - if (temp.Length == 0 || !pred(temp)) - { - break; - } - ret += temp; - } - return MovePosition(ret); - } - - private string MovePosition(string portion) - { - _bufferPos += portion.Length; - int index = portion.LastIndexOf('\n'); - if (index >= 0) - { - Column = new StringInfo(portion[(index + 1)..]).LengthInTextElements + 1; - Line += portion.Count((ch) => ch == '\n'); - } - else - { - Column += new StringInfo(portion).LengthInTextElements; - } - return portion; - } - - private string GetNextTextElement(int offset) - { - while (true) - { - string elem = StringInfo.GetNextTextElement(_buffer, _bufferPos + offset); - if (_bufferPos + offset + elem.Length < _buffer.Length) - { - return elem; - } - return elem; - } - } - - } -} diff --git a/tools/EdgeDB.DotnetTool/Lexer/SchemaLexer.cs b/tools/EdgeDB.DotnetTool/Lexer/SchemaLexer.cs deleted file mode 100644 index 6cf39bd6..00000000 --- a/tools/EdgeDB.DotnetTool/Lexer/SchemaLexer.cs +++ /dev/null @@ -1,252 +0,0 @@ -namespace EdgeDB.DotnetTool.Lexer -{ - internal class SchemaLexer - { - private readonly Dictionary _tokens = new() - { - { "module", TokenType.Module }, - { "{", TokenType.BeginBrace }, - { "}", TokenType.EndBrace }, - { "required", TokenType.Required }, - { "property", TokenType.Property }, - { "constraint", TokenType.Constraint }, - { "->", TokenType.TypeArrow }, - { "multi", TokenType.Multi }, - { "single", TokenType.Single }, - { "link", TokenType.Link }, - { "abstract", TokenType.Abstract }, - { ";", TokenType.Semicolon }, - { ":=", TokenType.Assignment }, - { "on", TokenType.On }, - { "extending", TokenType.Extending }, - { ",", TokenType.Comma }, - { "(", TokenType.BeginParenthesis }, - { ")", TokenType.EndParenthesis }, - { "annotation", TokenType.Annotation }, - { "type", TokenType.Type }, - { "index", TokenType.Index }, - { "scalar", TokenType.Scalar }, - { "function", TokenType.Function }, - }; - - private readonly SchemaBuffer _reader; - private readonly Stack _stack; - private Token? _previousToken; - - public SchemaLexer(string schema) - { - _reader = new SchemaBuffer(schema); - _stack = new(); - } - - public Token PeekToken() - { - if (_stack.Count == 0) - _stack.Push(ReadTokenInternal()); - - return _stack.Peek(); - } - - public Token ReadToken() - { - if (_stack.Count > 0) - return _stack.Pop(); - return ReadTokenInternal(); - } - - private Token ReadTokenInternal() - { - ReadWhitespace(); - - var elArr = _tokens.Select(x => x.Key.Length).Distinct(); - - foreach (var item in elArr) - { - if (_tokens.TryGetValue(_reader.Peek(item), out var token)) - { - _reader.Read(item); - - switch (token) - { - case TokenType.Assignment - or TokenType.TypeArrow - or TokenType.Extending - or TokenType.Constraint when _previousToken?.Type != TokenType.Abstract: - { - ReadWhitespace(); - List delimiters = new(new char[] { ';', '{' }); - if (token == TokenType.Extending && _reader.Peek(4) != "enum") - delimiters.Add(','); - - var value = ReadValue(token == TokenType.Assignment, delimiters.ToArray()); - return NewToken(token, value); - } - - case TokenType.Module - or TokenType.Type - or TokenType.Property - or TokenType.Link: - { - ReadWhitespace(); - var value = ReadValue(false, ';'); - return NewToken(token, value); - } - - case TokenType.Constraint when _previousToken?.Type == TokenType.Abstract: - { - // read value until the end of line or until ; - var value = ReadValue(true, ';'); - // read the semi colon - if (_reader.Peek(1) == ";") - value += _reader.Read(1); - return NewToken(TokenType.Constraint, value); - } - case TokenType.Function: - { - // read the value until the closing block - //var value = ReadValue(true, '{'); - - // read the name - var func = _reader.ReadUntil("("); - var funcParamsDepth = 1; - func += _reader.ReadWhile(x => - { - if (x == "(") - funcParamsDepth++; - - if (x == ")") - funcParamsDepth--; - - return funcParamsDepth > 0; - }); - func += _reader.Read(1); // the closing prarms brace - - // type arrow - func += ReadWhitespace(); - func += _reader.Read(2); - func += ReadWhitespace(); - // return type - func += ReadValue(); - func += ReadWhitespace(); - // check if its a bracket function - if (_reader.Peek(1) == "{") - { - var depth = 1; - _reader.Read(1); - func += _reader.ReadWhile(x => - { - if (x == "{") - depth++; - if (x == "}") - depth--; - - return depth > 0; - }); - - func += _reader.Read(1); - - // add semi colon - if (_reader.Peek(1) == ";") - func += _reader.Read(1); - - return NewToken(TokenType.Function, func); - } - else if (_reader.Peek(5) == "using") - { - // assume its a parentheses block? - func += _reader.Read(5); // read using - func += ReadWhitespace(); // read whitespace - func += _reader.Read(1); // read starting parantheses - var depth = 1; - func += _reader.ReadWhile(x => - { - if (x == "(") - depth++; - if (x == ")") - depth--; - - return depth > 0; - }); - - func += _reader.Read(1); // read closing parantheses - - // add semi colon - if (_reader.Peek(1) == ";") - func += _reader.Read(1); - - return NewToken(TokenType.Function, func); - } - - var a = _reader.Read(5); - - return NewToken(TokenType.Function, ""); - } - default: - return NewToken(token, null); - - } - - } - } - - var st = _reader.Peek(1); - - if (st == null || st.Length == 0) - { - // end of file - return NewToken(TokenType.EndOfFile); - } - - var c = _reader.Peek(1)[0]; - - // identifier - var val = ReadValue(false, ';'); - - return NewToken(TokenType.Identifier, val); - } - - private string ReadValue(bool ignoreSpace = false, params char[] delimiters) - { - // read untill we get to a quote, then read everything in the quote until whitespace - bool isEscaped = false; - return _reader.ReadWhile(x => - { - if (x is "'" or "\"" or "[" or "]" or "`" or "<" or ">") - isEscaped = !isEscaped; - - return isEscaped || ((ignoreSpace || !char.IsWhiteSpace(x, 0)) && (delimiters.Length == 0 || !delimiters.Contains(x[0]))); - }); - } - - public Token Expect(TokenType type) - { - var t = PeekToken(); - - if (type == TokenType.EndOfFile) - { - throw new EndOfStreamException("Unexpected end of file"); - } - - if (t.Type != type) - throw new ArgumentException($"Unexpected token! Expected {type} but got {t.Type} at {t.StartLine}:{t.StartPos}", nameof(type)); - - return ReadToken(); - } - - private Token NewToken(TokenType t, string? value = null) - { - var token = new Token - { - Type = t, - Value = value, - StartPos = _reader.Column, - StartLine = _reader.Line - }; - _previousToken = token; - return token; - } - - private string ReadWhitespace() - => _reader.ReadWhile(x => char.IsWhiteSpace(x, 0)); - } -} diff --git a/tools/EdgeDB.DotnetTool/Lexer/Token.cs b/tools/EdgeDB.DotnetTool/Lexer/Token.cs deleted file mode 100644 index 1e073f40..00000000 --- a/tools/EdgeDB.DotnetTool/Lexer/Token.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool.Lexer -{ - internal class Token - { - public TokenType Type { get; set; } - - public string? Value { get; set; } - - public int StartPos { get; set; } - - public int StartLine { get; set; } - - public int EndPos { get; set; } - - public int EndLine { get; set; } - } -} diff --git a/tools/EdgeDB.DotnetTool/Lexer/TokenType.cs b/tools/EdgeDB.DotnetTool/Lexer/TokenType.cs deleted file mode 100644 index 9812a551..00000000 --- a/tools/EdgeDB.DotnetTool/Lexer/TokenType.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool.Lexer -{ - internal enum TokenType - { - BeginBrace, - EndBrace, - Required, - Property, - Constraint, - TypeArrow, - Multi, - Link, - Abstract, - Semicolon, - Assignment, - Single, - On, - Extending, - Comma, - BeginParenthesis, - EndParenthesis, - Annotation, - Module, - Type, - Index, - Scalar, - Function, - - Identifier, - EndOfFile - } -} diff --git a/tools/EdgeDB.DotnetTool/Program.cs b/tools/EdgeDB.DotnetTool/Program.cs deleted file mode 100644 index 2d438436..00000000 --- a/tools/EdgeDB.DotnetTool/Program.cs +++ /dev/null @@ -1,7 +0,0 @@ -using CommandLine; -using EdgeDB.DotnetTool; -using System.Reflection; - -var commands = typeof(Program).Assembly.GetTypes().Where(x => x.GetInterfaces().Any(x => x == typeof(ICommand))); - -Parser.Default.ParseArguments(args, commands.ToArray()).WithParsed(t => t.Execute()); \ No newline at end of file diff --git a/tools/EdgeDB.DotnetTool/Properties/launchSettings.json b/tools/EdgeDB.DotnetTool/Properties/launchSettings.json deleted file mode 100644 index 27dbf5d1..00000000 --- a/tools/EdgeDB.DotnetTool/Properties/launchSettings.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "profiles": { - "EdgeDB.DotnetTool": { - "commandName": "Project", - "commandLineArgs": "generate -f C:\\Users\\lynch\\source\\repos\\EdgeDB\\src\\EdgeDB.DotnetTool\\test.esdl -v" - }, - "Profile 1": { - "commandName": "Executable" - } - } -} \ No newline at end of file diff --git a/tools/EdgeDB.DotnetTool/Schemas/ClassBuilder.cs b/tools/EdgeDB.DotnetTool/Schemas/ClassBuilder.cs deleted file mode 100644 index 81dfe3f7..00000000 --- a/tools/EdgeDB.DotnetTool/Schemas/ClassBuilder.cs +++ /dev/null @@ -1,225 +0,0 @@ -using System.Globalization; -using System.Text.RegularExpressions; - -namespace EdgeDB.DotnetTool -{ - internal class ClassBuilder - { - private readonly string _outputDir; - private readonly string _generatedNamespace; - - private readonly TextInfo _textInfo = new CultureInfo("en-US", false).TextInfo; - - - public ClassBuilder(string outputDir, string generatedNamespace) - { - _outputDir = outputDir; - _generatedNamespace = generatedNamespace; - } - - public void Generate(Module module, Func nameCallback) - { - var context = new ClassBuilderContext - { - Module = module, - NameCallback = nameCallback - }; - - // create the module folder - var folder = Path.Combine(_outputDir, _textInfo.ToTitleCase(module.Name!)); - Directory.CreateDirectory(folder); - - context.OutputDir = folder; - - foreach (var type in module.Types) - { - if (context.BuiltTypes.Contains(type)) - continue; - - context.Type = type; - GenerateType(type, folder, context); - - context.BuiltTypes.Add(type); - } - } - - public void GenerateType(Type t, string dir, ClassBuilderContext context) - { - var writer = new CodeWriter(); - - writer.AppendLine($"// Generated on {DateTimeOffset.UtcNow:O}"); - writer.AppendLine("using EdgeDB;"); - - string? name = t.Name; - - if (name != null && Regex.IsMatch(name, @"^[`'](.*?)[`']$")) - name = Regex.Match(name, @"^[`'](.*?)[`']$").Groups[1].Value; - - using (var _ = writer.BeginScope($"namespace {_generatedNamespace}")) - { - name = !IsValidDotnetName(name) - ? context.NameCallback($"{context.Module?.Name}::{name ?? "null"}") - : PascalUtils.ToPascalCase(name); - - t.BuiltName = name; - - if (IsEnum(t)) - { - writer.AppendLine("[EnumSerializer(SerializationMethod.Lower)]"); - using (var __ = writer.BeginScope($"public enum {name}")) - { - // extract values - foreach (Match match in Regex.Matches(t.Extending!, @"(?><| )(.*?)(?>,|>)")) - { - writer.AppendLine($"{match.Groups[1].Value},"); - } - } - } - else - { - // generate class - writer.AppendLine($"[EdgeDBType(\"{t.Name}\")]"); - using (var __ = writer.BeginScope($"public{(t.IsAbstract ? " abstract" : "")} class {name} : {ResolveTypename(context!, t.Extending) ?? "BaseObject"}")) - { - foreach (var prop in t.Properties) - { - if (context.BuildProperties.Contains(prop)) - continue; - - context.Property = prop; - GenerateProperty(writer, prop, context); - - context.BuildProperties.Add(prop); - } - } - } - } - - File.WriteAllText(Path.Combine(dir, $"{name}.g.cs"), writer.ToString()); - Console.WriteLine($"Wrote {name}.g.cs : {Path.Combine(dir, $"{name}.g.cs")}"); - } - - private string? GenerateProperty(CodeWriter writer, Property prop, ClassBuilderContext context) - { - // TODO: build a tree of contraints to use when applying attributes to props. - if (prop.IsStrictlyConstraint) - return null; - - // get the C# type if its a std type - var resolved = ResolveTypename(context, prop.Type); - var type = ResolveScalar(resolved)?.FullName ?? resolved; - - // convert to pascal case for name - var name = prop.Name; - - name = !IsValidDotnetName(name) - ? context.NameCallback($"{context.Module?.Name}::{context.Type?.Name}.{name ?? "null"}") - : PascalUtils.ToPascalCase(name); - - prop.BuiltName = name; - - if (type == null && prop.IsComputed && prop.ComputedValue != null) - { - var computed = prop.ComputedValue; - - var wrappedMatch = Regex.Match(computed, @"^\((.*?)\)$"); - - if (wrappedMatch.Success) - computed = wrappedMatch.Groups[1].Value; - - // check for backlink - if (computed.StartsWith(".<")) - { - // set the cardinaliry to multi since its a backlinl - prop.Cardinality = PropertyCardinality.Multi; - - var match = Regex.Match(computed, @"\[is (.+?)\]"); - - type = match.Success ? ResolveTypename(context, match.Groups[1].Value) : "BaseObject"; - } - else - { - // do a reverse lookup on the root function to see if we can decipher the type - computed = Regex.Replace(computed, @"^.+?::", _ => ""); - var returnType = QueryBuilder.ReverseLookupFunction(computed); - - if (returnType != null) - type = returnType.FullName; - else if (computed.StartsWith(".")) - { - // its a prop ref, generate it - var pName = Regex.Match(computed, @"^\.(\w+)").Groups[1].Value; - var p = context.Type?.Properties.FirstOrDefault(x => x.Name == pName)!; - - if (context.BuildProperties.Any(x => x.Name == p.Name)) - type = context.BuildProperties.FirstOrDefault(x => x.Name == p.Name)!.Type; - else - { - type = GenerateProperty(writer, p, context); - context.BuildProperties.Add(p); - } - } - else - { - type = "object"; - } - } - } - - if (prop.Cardinality == PropertyCardinality.Multi) - type = $"Set<{type}>"; - - writer.AppendLine($"[EdgeDBProperty(\"{prop.Name}\", IsLink = {Lower(prop.IsLink)}, IsRequired = {Lower(prop.Required)}, IsReadOnly = {Lower(prop.ReadOnly)}, IsComputed = {Lower(prop.IsComputed)})]"); - - // TODO: maybe remove set operator for readonly / computed? - writer.AppendLine($"public {type} {name} {{ get; set; }}"); - - prop.Type = type; - - return type; - - } - - public static System.Type? ResolveScalar(string? t) - => t == null - ? null - : PacketSerializer.GetDotnetType(Regex.Replace(t, @".+?::", m => "")); - - private string? ResolveTypename(ClassBuilderContext context, string? name) - { - if (name == null) - return null; - - if (name.StartsWith($"{context.Module!.Name}::")) - name = name[(context.Module.Name!.Length + 2)..]; - - // check our built types - if (context.BuiltTypes.Any(x => x.Name == name)) - { - return context.BuiltTypes.FirstOrDefault(x => x.Name == name)!.BuiltName; - } - else if (context.Module.Types.Any(x => x.Name == name)) // try building it if it has a name ref - { - var type = context.Module.Types.FirstOrDefault(x => x.Name == name)!; - GenerateType(type, context.OutputDir!, context); - context.BuiltTypes.Add(type); - return type.BuiltName; - } - - return name; - } - - internal static bool IsValidDotnetName(string? name) - { - if (name == null) - return false; - - return Regex.IsMatch(name, @"^[a-zA-Z@_](?>\w|@)+?$"); - } - - private static string Lower(bool val) - => val.ToString().ToLower(); - private static bool IsEnum(Type t) - => t.IsScalar && (t.Extending?.StartsWith("enum") ?? false); - } -} diff --git a/tools/EdgeDB.DotnetTool/Schemas/ClassBuilderContext.cs b/tools/EdgeDB.DotnetTool/Schemas/ClassBuilderContext.cs deleted file mode 100644 index ce03394f..00000000 --- a/tools/EdgeDB.DotnetTool/Schemas/ClassBuilderContext.cs +++ /dev/null @@ -1,27 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool -{ - internal class ClassBuilderContext - { - public List BuiltTypes { get; set; } = new(); - - public List BuildProperties { get; set; } = new(); - - public Func NameCallback { get; set; } = (s) => s; - - public List RequestedAttributes { get; set; } = new(); - - public Type? Type { get; set; } - - public Module? Module { get; set; } - - public Property? Property { get; set; } - - public string? OutputDir { get; set; } - } -} diff --git a/tools/EdgeDB.DotnetTool/Schemas/Models/Annotation.cs b/tools/EdgeDB.DotnetTool/Schemas/Models/Annotation.cs deleted file mode 100644 index 98071ee7..00000000 --- a/tools/EdgeDB.DotnetTool/Schemas/Models/Annotation.cs +++ /dev/null @@ -1,17 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool -{ - internal class Annotation - { - public string? Title { get; set; } - - public string? Description { get; set; } - - public string? Deprecated { get; set; } - } -} diff --git a/tools/EdgeDB.DotnetTool/Schemas/Models/Constraint.cs b/tools/EdgeDB.DotnetTool/Schemas/Models/Constraint.cs deleted file mode 100644 index d2ac2769..00000000 --- a/tools/EdgeDB.DotnetTool/Schemas/Models/Constraint.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool -{ - internal class Constraint - { - public string? Value { get; set; } - - public bool IsExpression { get; set; } - } -} diff --git a/tools/EdgeDB.DotnetTool/Schemas/Models/Module.cs b/tools/EdgeDB.DotnetTool/Schemas/Models/Module.cs deleted file mode 100644 index c4317935..00000000 --- a/tools/EdgeDB.DotnetTool/Schemas/Models/Module.cs +++ /dev/null @@ -1,15 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool -{ - internal class Module - { - public string? Name { get; set; } - - public List Types { get; set; } = new(); - } -} diff --git a/tools/EdgeDB.DotnetTool/Schemas/Models/Property.cs b/tools/EdgeDB.DotnetTool/Schemas/Models/Property.cs deleted file mode 100644 index 26cca462..00000000 --- a/tools/EdgeDB.DotnetTool/Schemas/Models/Property.cs +++ /dev/null @@ -1,46 +0,0 @@ -namespace EdgeDB.DotnetTool -{ - internal class Property - { - public Type? Parent { get; set; } - - public string? Name { get; set; } - - public string? Type { get; set; } - - public bool Required { get; set; } - - public PropertyCardinality Cardinality { get; set; } - - public string? DefaultValue { get; set; } - - public bool ReadOnly { get; set; } - - public List Constraints { get; set; } = new(); - - public Annotation? Annotation { get; set; } - - public List LinkProperties { get; set; } = new(); - - public bool IsStrictlyConstraint { get; set; } - - public bool IsAbstract { get; set; } - - public bool IsLink { get; set; } - - public bool IsComputed { get; set; } - - public string? ComputedValue { get; set; } - - public string? Extending { get; set; } - - // used for builder - public string? BuiltName { get; set; } - } - - public enum PropertyCardinality - { - One, - Multi - } -} diff --git a/tools/EdgeDB.DotnetTool/Schemas/Models/Type.cs b/tools/EdgeDB.DotnetTool/Schemas/Models/Type.cs deleted file mode 100644 index aa0f65c2..00000000 --- a/tools/EdgeDB.DotnetTool/Schemas/Models/Type.cs +++ /dev/null @@ -1,28 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool -{ - internal class Type - { - public Module? Parent { get; set; } - - public string? Name { get; set; } - - public string? Extending { get; set; } - - public bool IsAbstract { get; set; } - - public bool IsScalar { get; set; } - - public bool IsLink { get; set; } - - public List Properties { get; set; } = new(); - - // used for builder - public string? BuiltName { get; set; } - } -} diff --git a/tools/EdgeDB.DotnetTool/Schemas/SchemaReader.cs b/tools/EdgeDB.DotnetTool/Schemas/SchemaReader.cs deleted file mode 100644 index d00ddd57..00000000 --- a/tools/EdgeDB.DotnetTool/Schemas/SchemaReader.cs +++ /dev/null @@ -1,333 +0,0 @@ -using EdgeDB.DotnetTool.Lexer; - -namespace EdgeDB.DotnetTool -{ - internal class SchemaReader - { - private readonly SchemaLexer _lexer; - - public SchemaReader(string schema) - { - _lexer = new(schema); - } - - public List Read() - { - List modules = new(); - while (_lexer.PeekToken().Type != TokenType.EndOfFile) - { - modules.Add(ReadModule()); - } - return modules; - } - - private Module ReadModule() - { - var module = _lexer.Expect(TokenType.Module); - _lexer.Expect(TokenType.BeginBrace); - - var ret = new Module - { - Name = module.Value - }; - - while (_lexer.PeekToken().Type != TokenType.EndBrace) - { - var type = ReadModuleType(ret); - if (type != null) - ret.Types.Add(type); - } - - ExpectEndOfBody(); - - return ret; - } - - private Type? ReadModuleType(Module module) - { - bool isScalar = false; - bool isAbstract = false; - - var type = _lexer.ReadToken(); - - if (type.Type == TokenType.Scalar) - { - isScalar = true; - type = _lexer.ReadToken(); - } - - if (type.Type == TokenType.Abstract) - { - isAbstract = true; - type = _lexer.ReadToken(); - } - - switch (type.Type) - { - case TokenType.Type: - return ReadType(module, type, isScalar, isAbstract); - case TokenType.Annotation when isAbstract: - { - // skip, useless - // Colin 03-04-2022 - _ = _lexer.ReadToken(); // read value - _ = _lexer.ReadToken(); // skip semicolon - return null; - } - case TokenType.Constraint when isAbstract: - { - // store somewhere - return null; - } - case TokenType.Function: - { - // store somewhere - return null; - } - case TokenType.Link: - { - return ReadType(module, type, isScalar, isAbstract, true); - } - case TokenType.EndOfFile: - { - // shouldn't happen? - return null; - } - default: - { - // skip? - - return null; - } - } - } - - private Type? ReadType(Module module, Token type, bool isScalar, bool isAbstract, bool isLink = false) - { - var ret = new Type - { - Parent = module, - Name = type.Value, - IsScalar = isScalar, - IsAbstract = isAbstract, - IsLink = isLink, - }; - - if (_lexer.PeekToken().Type == TokenType.Semicolon) - { - // empty shape? - _lexer.Expect(TokenType.Semicolon); - return null; - } - - while (_lexer.PeekToken().Type != TokenType.BeginBrace) - { - var other = _lexer.ReadToken(); - - switch (other.Type) - { - case TokenType.Extending: - { - if (other.Value!.StartsWith("enum<")) - { - var t = new List(); - while (_lexer.PeekToken().Type == TokenType.Identifier) - { - t.Add(_lexer.ReadToken()); - } - - ret.Extending = $"{other.Value} {string.Join(" ", t.Select(x => x.Value))}"; - // read semi colon - if (_lexer.PeekToken().Type == TokenType.Semicolon) - _lexer.ReadToken(); - - - goto endLabel; - } - else - { - ret.IsAbstract = true; - ret.Extending = other.Value; - - // read semi colon - if (_lexer.PeekToken().Type == TokenType.Semicolon) - _lexer.ReadToken(); - - if (_lexer.PeekToken().Type == TokenType.Comma) - { - while (_lexer.PeekToken().Type != TokenType.BeginBrace) - { - _lexer.ReadToken(); // comma - var a = _lexer.ReadToken(); - ret.Extending += $", {a.Value}"; - } - } - - goto endLabel; - } - } - default: - throw new FormatException($"Unexpected token type {other.Type} at {other.StartLine}:{other.StartPos}"); - } - } - - endLabel: - - if (_lexer.PeekToken().Type == TokenType.BeginBrace) - { - _lexer.Expect(TokenType.BeginBrace); - // read inner - while (_lexer.PeekToken().Type != TokenType.EndBrace) - { - ret.Properties.Add(ReadProperty(ret)); - } - - ExpectEndOfBody(); - } - - return ret; - } - - private Property ReadProperty(Type type) - { - var prop = new Property() { Parent = type }; - - while (_lexer.PeekToken().Type is not TokenType.Property and not TokenType.Link) - { - var token = _lexer.ReadToken(); - - switch (token.Type) - { - case TokenType.Required: - prop.Required = true; - break; - case TokenType.Single: - prop.Cardinality = PropertyCardinality.One; - break; - case TokenType.Multi: - prop.Cardinality = PropertyCardinality.Multi; - break; - case TokenType.Abstract: - prop.IsAbstract = true; - break; - case TokenType.Constraint: - prop.IsStrictlyConstraint = true; - prop.Constraints.Add(new Constraint - { - Value = token.Value, - IsExpression = false, - }); - - if (_lexer.PeekToken().Type == TokenType.Semicolon) - _lexer.ReadToken(); - - return prop; - } - } - - var propDeclaration = _lexer.ReadToken(); - prop.Name = propDeclaration.Value; - prop.IsLink = propDeclaration.Type == TokenType.Link; - - // read type - var propertyDeclarerToken = _lexer.ReadToken(); - if (propertyDeclarerToken.Type == TokenType.TypeArrow) - { - prop.Type = propertyDeclarerToken.Value; - } - else if (propertyDeclarerToken.Type == TokenType.Assignment) - { - prop.IsComputed = true; - prop.ComputedValue = propertyDeclarerToken.Value; - } - else if (propertyDeclarerToken.Type == TokenType.Extending) - { - // read the extending value until type assignment - var typeAssignment = _lexer.Expect(TokenType.TypeArrow); - prop.Extending = propertyDeclarerToken.Value; - prop.Type = typeAssignment.Value; - } - else - throw new FormatException($"Expected type arrow or assignment but got {propertyDeclarerToken.Type} at {propertyDeclarerToken.StartLine}:{propertyDeclarerToken.StartPos}"); - - - // check for body - if (_lexer.PeekToken().Type == TokenType.BeginBrace) - { - _lexer.Expect(TokenType.BeginBrace); - - while (_lexer.PeekToken().Type != TokenType.EndBrace) - { - var peeked = _lexer.PeekToken(); - - switch (peeked.Type) - { - case TokenType.Constraint: - prop.Constraints.Add(ReadConstraint()); - break; - case TokenType.Annotation: - prop.Annotation = ReadAnnotation(); - break; - case TokenType.Property when prop.IsLink: - prop.LinkProperties.Add(ReadProperty(type)); - break; - case TokenType.Identifier when peeked.Value == "default": - { - _lexer.Expect(TokenType.Identifier); - var assignment = _lexer.Expect(TokenType.Assignment); - - if (_lexer.PeekToken().Type == TokenType.Semicolon) - _lexer.ReadToken(); - - prop.DefaultValue = assignment.Value; - } - break; - - default: - throw new FormatException($"Unexpected token, expected constraint or annotation but got {peeked.Type} at {peeked.StartLine}:{peeked.StartPos}"); - } - } - - ExpectEndOfBody(); - } - else - _lexer.Expect(TokenType.Semicolon); - - return prop; - } - - private Constraint ReadConstraint() - { - var constraint = _lexer.Expect(TokenType.Constraint); - - if (_lexer.PeekToken().Type == TokenType.On && constraint.Value == "expression") - { - //var val = ""; - _lexer.Expect(TokenType.BeginParenthesis); - - return new Constraint - { - IsExpression = true, - }; - } - - if (_lexer.PeekToken().Type == TokenType.Semicolon) - _lexer.ReadToken(); - - return new Constraint - { - IsExpression = false, - Value = constraint.Value - }; - } - - private static Annotation ReadAnnotation() => new() { }; - - private void ExpectEndOfBody() - { - _lexer.Expect(TokenType.EndBrace); - if (_lexer.PeekToken().Type == TokenType.Semicolon) - _lexer.ReadToken(); - } - } -} diff --git a/tools/EdgeDB.DotnetTool/Util/PascalUtils.cs b/tools/EdgeDB.DotnetTool/Util/PascalUtils.cs deleted file mode 100644 index 36c7dc5f..00000000 --- a/tools/EdgeDB.DotnetTool/Util/PascalUtils.cs +++ /dev/null @@ -1,45 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace EdgeDB.DotnetTool -{ - internal static class PascalUtils - { - readonly static Regex _invalidCharsRgx = new("[^_a-zA-Z0-9]", RegexOptions.Compiled); - - readonly static Regex _whiteSpace = new(@"(?<=\s)", RegexOptions.Compiled); - - readonly static Regex _startsWithLowerCaseChar = new("^[a-z]", RegexOptions.Compiled); - - readonly static Regex _firstCharFollowedByUpperCasesOnly = new("(?<=[A-Z])[A-Z0-9]+$", RegexOptions.Compiled); - - readonly static Regex _lowerCaseNextToNumber = new("(?<=[0-9])[a-z]", RegexOptions.Compiled); - - readonly static Regex _upperCaseInside = new("(?<=[A-Z])[A-Z]+?((?=[A-Z][a-z])|(?=[0-9]))", RegexOptions.Compiled); - - public static string ToPascalCase(string? original) - { - if (original == null) - return ""; - - // replace white spaces with undescore, then replace all invalid chars with empty string - var pascalCase = _invalidCharsRgx.Replace(_whiteSpace.Replace(original, "_"), string.Empty) - // split by underscores - .Split(new char[] { '_' }, StringSplitOptions.RemoveEmptyEntries) - // set first letter to uppercase - .Select(w => _startsWithLowerCaseChar.Replace(w, m => m.Value.ToUpper())) - // replace second and all following upper case letters to lower if there is no next lower (ABC -> Abc) - .Select(w => _firstCharFollowedByUpperCasesOnly.Replace(w, m => m.Value.ToLower())) - // set upper case the first lower case following a number (Ab9cd -> Ab9Cd) - .Select(w => _lowerCaseNextToNumber.Replace(w, m => m.Value.ToUpper())) - // lower second and next upper case letters except the last if it follows by any lower (ABcDEf -> AbcDef) - .Select(w => _upperCaseInside.Replace(w, m => m.Value.ToLower())); - - return string.Concat(pascalCase); - } - } -} diff --git a/tools/EdgeDB.DotnetTool/dbschema/default.esdl b/tools/EdgeDB.DotnetTool/dbschema/default.esdl deleted file mode 100644 index 2d960308..00000000 --- a/tools/EdgeDB.DotnetTool/dbschema/default.esdl +++ /dev/null @@ -1,141 +0,0 @@ -module default { - - scalar type Genre extending enum; - - abstract link movie_character { - property character_name -> str; - } - abstract type Person { - required property name -> str { - constraint exclusive; - }; - } - - type Villain extending Person { - link nemesis -> Hero; - } - - type Hero extending Person { - property secret_identity -> str; - property number_of_movies -> int64; - multi link villains := . Genre; - property rating -> float64; - required property title -> str { - constraint exclusive; - }; - required property release_year -> int16 { - default := datetime_get(datetime_current(), 'year'); - } - multi link characters extending movie_character -> Person; - link profile -> Profile { - constraint exclusive; - } - } - - type Profile { - property plot_summary -> str; - } - - type User { - required property username -> str; - required link favourite_movie -> Movie; - } - - type MovieShape { - } - - abstract type HasName { - property name -> str; - } - abstract type HasAge { - property age -> int64; - } - - scalar type bag_seq extending sequence; - - type Bag extending HasName, HasAge { - property secret_identity -> str; - property genre -> Genre; - property boolField -> bool; - property datetimeField -> datetime; - property localDateField -> cal::local_date; - property localTimeField -> cal::local_time; - property localDateTimeField -> cal::local_datetime; - property durationField -> duration; - property decimalField -> decimal; - property int64Field -> int64; - property int32Field -> int32; - property int16Field -> int16; - property float32Field -> float32; - property float64Field -> float64; - property bigintField -> bigint; - required multi property stringsMulti -> str; - property stringsArr -> array; - multi property stringMultiArr -> array; - property namedTuple -> tuple; - property unnamedTuple -> tuple; - property enumArr -> array; - property seqField -> bag_seq; - } - - type Simple extending HasName, HasAge {} - - # Unicode handling - # https://github.com/edgedb/edgedb/blob/master/tests/schemas/dump02_default.esdl - - abstract annotation `🍿`; - - abstract constraint `🚀🍿`(max: int64) extending max_len_value; - - function `💯`(NAMED ONLY `🙀`: int64) -> int64 { - using ( - SELECT 100 - `🙀` - ); - - annotation `🍿` := 'fun!🚀'; - volatility := 'Immutable'; - } - - type `S p a M` { - required property `🚀` -> int32; - property c100 := (SELECT `💯`(`🙀` := .`🚀`)); - } - - type A { - required link `s p A m 🤞` -> `S p a M`; - } - - scalar type 你好 extending str; - - scalar type مرحبا extending 你好 { - constraint `🚀🍿`(100); - }; - - scalar type `🚀🚀🚀` extending مرحبا; - - type Łukasz { - required property `Ł🤞` -> `🚀🚀🚀` { - default := <`🚀🚀🚀`>'你好🤞' - } - index on (.`Ł🤞`); - - link `Ł💯` -> A { - property `🙀🚀🚀🚀🙀` -> `🚀🚀🚀`; - property `🙀مرحبا🙀` -> مرحبا { - constraint `🚀🍿`(200); - } - }; - } - -}; - -module `💯💯💯` { - function `🚀🙀🚀`(`🤞`: default::`🚀🚀🚀`) -> default::`🚀🚀🚀` - using ( - SELECT (`🤞` ++ 'Ł🙀') - ); -}; \ No newline at end of file diff --git a/tools/EdgeDB.DotnetTool/dbschema/migrations/00001.edgeql b/tools/EdgeDB.DotnetTool/dbschema/migrations/00001.edgeql deleted file mode 100644 index 6232b5a9..00000000 --- a/tools/EdgeDB.DotnetTool/dbschema/migrations/00001.edgeql +++ /dev/null @@ -1,117 +0,0 @@ -CREATE MIGRATION m1g3fpbyrx4lwrodm7wrekqq4pkbz6otz7gcwmwz43l5mca3qtc47q - ONTO initial -{ - CREATE MODULE `💯💯💯` IF NOT EXISTS; - CREATE ABSTRACT ANNOTATION default::`🍿`; - CREATE FUNCTION default::`💯`(NAMED ONLY `🙀`: std::int64) -> std::int64 { - SET volatility := 'Immutable'; - CREATE ANNOTATION default::`🍿` := 'fun!🚀'; - USING (SELECT - (100 - `🙀`) - ) - ;}; - CREATE SCALAR TYPE default::Genre EXTENDING enum; - CREATE ABSTRACT TYPE default::HasAge { - CREATE PROPERTY age -> std::int64; - }; - CREATE ABSTRACT TYPE default::HasName { - CREATE PROPERTY name -> std::str; - }; - CREATE SCALAR TYPE default::bag_seq EXTENDING std::sequence; - CREATE TYPE default::Bag EXTENDING default::HasName, default::HasAge { - CREATE PROPERTY enumArr -> array; - CREATE PROPERTY bigintField -> std::bigint; - CREATE PROPERTY boolField -> std::bool; - CREATE PROPERTY datetimeField -> std::datetime; - CREATE PROPERTY decimalField -> std::decimal; - CREATE PROPERTY durationField -> std::duration; - CREATE PROPERTY float32Field -> std::float32; - CREATE PROPERTY float64Field -> std::float64; - CREATE PROPERTY genre -> default::Genre; - CREATE PROPERTY int16Field -> std::int16; - CREATE PROPERTY int32Field -> std::int32; - CREATE PROPERTY int64Field -> std::int64; - CREATE PROPERTY localDateField -> cal::local_date; - CREATE PROPERTY localDateTimeField -> cal::local_datetime; - CREATE PROPERTY localTimeField -> cal::local_time; - CREATE PROPERTY namedTuple -> tuple; - CREATE PROPERTY secret_identity -> std::str; - CREATE PROPERTY seqField -> default::bag_seq; - CREATE MULTI PROPERTY stringMultiArr -> array; - CREATE PROPERTY stringsArr -> array; - CREATE REQUIRED MULTI PROPERTY stringsMulti -> std::str; - CREATE PROPERTY unnamedTuple -> tuple; - }; - CREATE ABSTRACT CONSTRAINT default::`🚀🍿`(max: std::int64) EXTENDING std::max_len_value; - CREATE TYPE default::A; - CREATE SCALAR TYPE default::你好 EXTENDING std::str; - CREATE SCALAR TYPE default::مرحبا EXTENDING default::你好 { - CREATE CONSTRAINT default::`🚀🍿`(100); - }; - CREATE SCALAR TYPE default::`🚀🚀🚀` EXTENDING default::مرحبا; - CREATE TYPE default::Łukasz { - CREATE LINK `Ł💯` -> default::A { - CREATE PROPERTY `🙀مرحبا🙀` -> default::مرحبا { - CREATE CONSTRAINT default::`🚀🍿`(200); - }; - CREATE PROPERTY `🙀🚀🚀🚀🙀` -> default::`🚀🚀🚀`; - }; - CREATE REQUIRED PROPERTY `Ł🤞` -> default::`🚀🚀🚀` { - SET default := ('你好🤞'); - }; - CREATE INDEX ON (.`Ł🤞`); - }; - CREATE TYPE default::`S p a M` { - CREATE REQUIRED PROPERTY `🚀` -> std::int32; - CREATE PROPERTY c100 := (SELECT - default::`💯`(`🙀` := .`🚀`) - ); - }; - CREATE FUNCTION `💯💯💯`::`🚀🙀🚀`(`🤞`: default::`🚀🚀🚀`) -> default::`🚀🚀🚀` USING (SELECT - (`🤞` ++ 'Ł🙀') - ); - CREATE ABSTRACT LINK default::movie_character { - CREATE PROPERTY character_name -> std::str; - }; - CREATE ABSTRACT TYPE default::Person { - CREATE REQUIRED PROPERTY name -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - }; - CREATE TYPE default::Profile { - CREATE PROPERTY plot_summary -> std::str; - }; - CREATE TYPE default::Movie { - CREATE MULTI LINK characters EXTENDING default::movie_character -> default::Person; - CREATE LINK profile -> default::Profile { - CREATE CONSTRAINT std::exclusive; - }; - CREATE PROPERTY genre -> default::Genre; - CREATE PROPERTY rating -> std::float64; - CREATE REQUIRED PROPERTY release_year -> std::int16 { - SET default := (std::datetime_get(std::datetime_current(), 'year')); - }; - CREATE REQUIRED PROPERTY title -> std::str { - CREATE CONSTRAINT std::exclusive; - }; - }; - ALTER TYPE default::A { - CREATE REQUIRED LINK `s p A m 🤞` -> default::`S p a M`; - }; - CREATE TYPE default::Simple EXTENDING default::HasName, default::HasAge; - CREATE TYPE default::Hero EXTENDING default::Person { - CREATE PROPERTY number_of_movies -> std::int64; - CREATE PROPERTY secret_identity -> std::str; - }; - CREATE TYPE default::Villain EXTENDING default::Person { - CREATE LINK nemesis -> default::Hero; - }; - ALTER TYPE default::Hero { - CREATE MULTI LINK villains := (. default::Movie; - CREATE REQUIRED PROPERTY username -> std::str; - }; - CREATE TYPE default::MovieShape; -}; diff --git a/tools/EdgeDB.DotnetTool/edgedb.toml b/tools/EdgeDB.DotnetTool/edgedb.toml deleted file mode 100644 index 597ff333..00000000 --- a/tools/EdgeDB.DotnetTool/edgedb.toml +++ /dev/null @@ -1,2 +0,0 @@ -[edgedb] -server-version = "1.1" diff --git a/tools/EdgeDB.DotnetTool/test.esdl b/tools/EdgeDB.DotnetTool/test.esdl deleted file mode 100644 index b67692cc..00000000 --- a/tools/EdgeDB.DotnetTool/test.esdl +++ /dev/null @@ -1,113 +0,0 @@ -module default { - abstract annotation `🍿`; - abstract constraint `🚀🍿`(max: std::int64) extending std::max_len_value; - function `💯`(named only `🙀`: std::int64) -> std::int64 { - volatility := 'Immutable'; - annotation default::`🍿` := 'fun!🚀'; - using (select - (100 - `🙀`) - ) - ;}; - abstract link movie_character { - property character_name -> std::str; - }; - scalar type Genre extending enum; - scalar type bag_seq extending std::sequence; - scalar type مرحبا extending default::你好 { - constraint default::`🚀🍿`(100); - }; - scalar type 你好 extending std::str; - scalar type `🚀🚀🚀` extending default::مرحبا; - type A { - required link `s p A m 🤞` -> default::`S p a M`; - }; - type Bag extending default::HasName, default::HasAge { - property bigintField -> std::bigint; - property boolField -> std::bool; - property datetimeField -> std::datetime; - property decimalField -> std::decimal; - property durationField -> std::duration; - property enumArr -> array; - property float32Field -> std::float32; - property float64Field -> std::float64; - property genre -> default::Genre; - property int16Field -> std::int16; - property int32Field -> std::int32; - property int64Field -> std::int64; - property localDateField -> cal::local_date; - property localDateTimeField -> cal::local_datetime; - property localTimeField -> cal::local_time; - property namedTuple -> tuple; - property secret_identity -> std::str; - property seqField -> default::bag_seq; - multi property stringMultiArr -> array; - property stringsArr -> array; - required multi property stringsMulti -> std::str; - property unnamedTuple -> tuple; - }; - abstract type HasAge { - property age -> std::int64; - }; - abstract type HasName { - property name -> std::str; - }; - type Hero extending default::Person { - multi link villains := (. std::int64; - property secret_identity -> std::str; - }; - type Movie { - multi link characters extending default::movie_character -> default::Person; - link profile -> default::Profile { - constraint std::exclusive; - }; - property genre -> default::Genre; - property rating -> std::float64; - required property release_year -> std::int16 { - default := (std::datetime_get(std::datetime_current(), 'year')); - }; - required property title -> std::str { - constraint std::exclusive; - }; - }; - type MovieShape; - abstract type Person { - required property name -> std::str { - constraint std::exclusive; - }; - }; - type Profile { - property plot_summary -> std::str; - }; - type `S p a M` { - property c100 := (select - default::`💯`(`🙀` := .`🚀`) - ); - required property `🚀` -> std::int32; - }; - type Simple extending default::HasName, default::HasAge; - type User { - required link favourite_movie -> default::Movie; - required property username -> std::str; - }; - type Villain extending default::Person { - link nemesis -> default::Hero; - }; - type Łukasz { - index on (.`Ł🤞`); - link `Ł💯` -> default::A { - property `🙀مرحبا🙀` -> default::مرحبا { - constraint default::`🚀🍿`(200); - }; - property `🙀🚀🚀🚀🙀` -> default::`🚀🚀🚀`; - }; - required property `Ł🤞` -> default::`🚀🚀🚀` { - default := ('你好🤞'); - }; - }; -}; -module `💯💯💯` { - function `🚀🙀🚀`(`🤞`: default::`🚀🚀🚀`) -> default::`🚀🚀🚀` using (select - (`🤞` ++ 'Ł🙀') - ); -}; \ No newline at end of file From 7d4ad91b41130adf930fd33713d78f3217fbacec Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Tue, 23 Aug 2022 12:08:00 -0300 Subject: [PATCH 03/13] Working generate command --- EdgeDB.Net.sln | 6 + src/EdgeDB.Net.CLI/Commands/Generate.cs | 110 ++--- src/EdgeDB.Net.CLI/EdgeQLParser.cs | 325 +++++++++++++ .../FrameworkWriters/CSharpLanguageWriter.cs | 11 - .../FrameworkWriters/ILanguageWriter.cs | 18 - src/EdgeDB.Net.CLI/Program.cs | 34 +- .../Properties/launchSettings.json | 2 +- src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs | 455 ++++++++++++++++++ src/EdgeDB.Net.CLI/Utils/HashUtils.cs | 19 + src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs | 24 +- .../Utils/QueryErrorFormatter.cs | 37 ++ src/EdgeDB.Net.CLI/Utils/TextUtils.cs | 27 ++ .../Clients/EdgeDBBinaryClient.cs | 12 +- src/EdgeDB.Net.Driver/Codecs/Tuple.cs | 12 +- .../Utils/ReflectionUtils.cs | 6 + test.edgeql | 3 - 16 files changed, 975 insertions(+), 126 deletions(-) create mode 100644 src/EdgeDB.Net.CLI/EdgeQLParser.cs delete mode 100644 src/EdgeDB.Net.CLI/FrameworkWriters/CSharpLanguageWriter.cs delete mode 100644 src/EdgeDB.Net.CLI/FrameworkWriters/ILanguageWriter.cs create mode 100644 src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs create mode 100644 src/EdgeDB.Net.CLI/Utils/HashUtils.cs create mode 100644 src/EdgeDB.Net.CLI/Utils/QueryErrorFormatter.cs create mode 100644 src/EdgeDB.Net.CLI/Utils/TextUtils.cs delete mode 100644 test.edgeql diff --git a/EdgeDB.Net.sln b/EdgeDB.Net.sln index 85aa8681..9ede2048 100644 --- a/EdgeDB.Net.sln +++ b/EdgeDB.Net.sln @@ -35,6 +35,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Examples.ExampleTODO EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Net.CLI", "src\EdgeDB.Net.CLI\EdgeDB.Net.CLI.csproj", "{77D1980E-9835-4D16-B7B4-6CB5A4BD570C}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Generated", "EdgeDB.Generated\EdgeDB.Generated.csproj", "{A7F2328A-F2CE-43AB-99A3-41413BB735F6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -85,6 +87,10 @@ Global {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Debug|Any CPU.Build.0 = Debug|Any CPU {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Release|Any CPU.ActiveCfg = Release|Any CPU {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Release|Any CPU.Build.0 = Release|Any CPU + {A7F2328A-F2CE-43AB-99A3-41413BB735F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7F2328A-F2CE-43AB-99A3-41413BB735F6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7F2328A-F2CE-43AB-99A3-41413BB735F6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7F2328A-F2CE-43AB-99A3-41413BB735F6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/EdgeDB.Net.CLI/Commands/Generate.cs b/src/EdgeDB.Net.CLI/Commands/Generate.cs index 90f218dc..06e8b497 100644 --- a/src/EdgeDB.Net.CLI/Commands/Generate.cs +++ b/src/EdgeDB.Net.CLI/Commands/Generate.cs @@ -3,6 +3,7 @@ using EdgeDB.CLI.Utils; using EdgeDB.Codecs; using System.Reflection; +using System.Text.RegularExpressions; namespace EdgeDB.CLI; @@ -18,13 +19,11 @@ public class Generate : ConnectionArguments, ICommand [Option('n', "generated-project-name", HelpText = "The name of the generated project.")] public string GeneratedProjectName { get; set; } = "EdgeDB.Generated"; - [Option("target", HelpText = "The language the generated code should target\n\nValid inputs are: C#, F#, and VB")] - public string? TargetLanguage { get; set; } - + [Option('f', "force", HelpText = "Force regeneration of files")] + public bool Force { get; set; } + public async Task ExecuteAsync() { - TargetLanguage = (TargetLanguage ?? "c#").ToLower(); - // get connection info var connection = GetConnection(); @@ -35,83 +34,60 @@ public async Task ExecuteAsync() await client.ConnectAsync(); var projectRoot = ProjectUtils.GetProjectRoot(); - - OutputDirectory ??= Path.Combine(projectRoot, "EdgeDB.Generated"); - if(GenerateProject && !Directory.Exists(OutputDirectory)) + OutputDirectory ??= Path.Combine(projectRoot, GeneratedProjectName); + + if (GenerateProject && !Directory.Exists(OutputDirectory)) { - Console.WriteLine("Creating EdgeDB.Generated project..."); - await ProjectUtils.CreateGeneratedProjectAsync(projectRoot, GeneratedProjectName, TargetLanguage); + Console.WriteLine($"Creating project {GeneratedProjectName}..."); + await ProjectUtils.CreateGeneratedProjectAsync(projectRoot, GeneratedProjectName); } // find edgeql files - var edgeqlFiles = ProjectUtils.GetTargetEdgeQLFiles(projectRoot); - - Console.WriteLine($"Generating {edgeqlFiles.Count()} files..."); + var edgeqlFiles = ProjectUtils.GetTargetEdgeQLFiles(projectRoot).ToArray(); - var queriesExtensionWriter = CreateDefaultReader(); + // compute the hashes for each file + string[] hashs = edgeqlFiles.Select(x => HashUtils.HashEdgeQL(File.ReadAllText(x))).ToArray(); - var languageWriter = ILanguageWriter.GetWriter(TargetLanguage); + Console.WriteLine($"Generating {edgeqlFiles.Length} files..."); - foreach (var file in edgeqlFiles) + for(int i = 0; i != edgeqlFiles.Length; i++) { + var file = edgeqlFiles[i]; + var hash = hashs[i]; + var targetFileName = TextUtils.ToPascalCase(Path.GetFileName(file).Split('.')[0]); + var targetOutputPath = Path.Combine(projectRoot, GeneratedProjectName, $"{targetFileName}.g.cs"); var edgeql = File.ReadAllText(file); - var parseResult = await client.ParseAsync(edgeql, Cardinality.Many, IOFormat.Binary, Capabilities.All, default); - - ProcessEdgeQLFile(parseResult, languageWriter, queriesExtensionWriter); - } - } - - private void ProcessEdgeQLFile(EdgeDBBinaryClient.ParseResult parseResult, ILanguageWriter writer, - CodeWriter extWriter) - { - // generate the type - var codecType = GetTypeOfCodec(parseResult.OutCodec.Codec); - } - - private CodecTypeInfo GetTypeOfCodec(ICodec codec, string? name = null) - { - CodecTypeInfo typeInfo; - // TODO: complete codec parsing + if (!Force && File.Exists(targetOutputPath)) + { + // check the hashes + var hashHeader = Regex.Match(File.ReadAllLines(targetOutputPath)[1], @"\/\/ edgeql:([0-9a-fA-F]{64})"); + + if(hashHeader.Success && hashHeader.Groups[1].Value == hash) + { + Console.WriteLine($"{i + 1}: Skipping {file}: File already generated"); + continue; + } + } - typeInfo = codec switch - { - Codecs.Object obj => new CodecTypeInfo + try { - IsObject = true, - Children = obj.InnerCodecs.Select((x, i) => GetTypeOfCodec(x, obj.PropertyNames[i])), - TypeName = name - }, - ICodec set when ReflectionUtils.IsSubclassOfRawGeneric(set.GetType(), typeof(Set<>)) => new CodecTypeInfo + var result = await EdgeQLParser.ParseAndGenerateAsync(client, edgeql, GeneratedProjectName, hash, targetFileName); + File.WriteAllText(targetOutputPath, result.Code); + } + catch (EdgeDBErrorException error) { - - Children = new[] { GetTypeOfCodec((ICodec)set.GetType().GetField("_innerCodec", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(set)!) } + Console.WriteLine($"Failed to parse {file} (line {error.ErrorResponse.Attributes.FirstOrDefault(x => x.Code == 65523).ToString() ?? "??"}, column {error.ErrorResponse.Attributes.FirstOrDefault(x => x.Code == 65524).ToString() ?? "??"}):"); + Console.WriteLine(error.Message); + Console.WriteLine(QueryErrorFormatter.FormatError(edgeql, error)); + Console.WriteLine($"{i + 1}: Skipping {file}: File contains errors"); + continue; } - }; - - typeInfo.Name = name ?? typeInfo.Name; - } - private class CodecTypeInfo - { - public bool IsArray { get; set; } - public bool IsObject { get; set; } - public bool IsTuple { get; set; } - public string? Name { get; set; } - public string? TypeName { get; set; } - public IEnumerable? Children { get; set; } - } + Console.WriteLine($"{i + 1}: {file} => {targetOutputPath}"); + } - private CodeWriter CreateDefaultReader() - { - var writer = new CodeWriter(); - writer.AppendLine("// AUTOGENERATED: DO NOT MODIFY"); - writer.AppendLine($"// Generated on {DateTime.UtcNow:O}"); - writer.AppendLine($"using EdgeDB;"); - writer.AppendLine($"using {GeneratedProjectName};"); - writer.AppendLine(); - - return writer; + Console.WriteLine("Generation complete!"); } -} +} \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/EdgeQLParser.cs b/src/EdgeDB.Net.CLI/EdgeQLParser.cs new file mode 100644 index 00000000..8f5be5da --- /dev/null +++ b/src/EdgeDB.Net.CLI/EdgeQLParser.cs @@ -0,0 +1,325 @@ +using EdgeDB.CLI.Utils; +using EdgeDB.Codecs; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI +{ + internal class EdgeQLParser + { + public static async Task ParseAndGenerateAsync(EdgeDBTcpClient client, string edgeql, string @namespace, string hash, string name) + { + var parseResult = await client.ParseAsync(edgeql, Cardinality.Many, IOFormat.Binary, Capabilities.All, default); + + return GenerateCSharpFromEdgeQL(@namespace, edgeql, hash, name, parseResult); + } + + public class GenerationResult + { + public string? Code { get; set; } + public string? EdgeQLHash { get; set; } + public string? ExecuterClassName { get; set; } + public string? ReturnResult { get; set; } + public IEnumerable? Parameters { get; set; } + } + + private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, string edgeql, string hash, string resultName, EdgeDBBinaryClient.ParseResult parseResult) + { + var codecType = GetTypeOfCodec(parseResult.OutCodec.Codec, $"{resultName} Result"); + + // create the class writer + + var writer = new CodeWriter(); + writer.AppendLine("// AUTOGENERATED: DO NOT MODIFY"); + writer.AppendLine($"// edgeql:{hash}"); + writer.AppendLine($"// Generated on {DateTime.UtcNow:O}"); + writer.AppendLine("#nullable enable"); + writer.AppendLine($"using EdgeDB;"); + writer.AppendLine(); + writer.AppendLine($"namespace {@namespace};"); + writer.AppendLine(); + + var refTypes = new List(); + var compliledTypes = new List<(CodecTypeInfo Info, string Reference)?>(); + + var mainResult = BuildTypes(codecType, out var typeName, refTypes); + + compliledTypes.Add((codecType, mainResult)); + + if (refTypes.Any() || codecType.IsObject) + { + var seenTypes = new HashSet(refTypes) { codecType }; + var refStack = new Stack(refTypes); + + writer.AppendLine("#region Types"); + writer.AppendLine(mainResult); + + // circle dependency safe! + while (refStack.TryPop(out var typeInfo)) + { + var complRef = compliledTypes.FirstOrDefault(x => x!.Value.Info.BodyEquals(typeInfo)); + + if (complRef is not null) + { + writer.AppendLine(complRef.Value.Reference); + continue; + } + + var newTypes = new List(); + var result = BuildTypes(typeInfo, out _, newTypes); + + if (newTypes.Any()) + foreach (var newType in newTypes.Where(x => !seenTypes.TryGetValue(x, out _))) + refStack.Push(newType); + + writer.AppendLine(result); + compliledTypes.Add((typeInfo, result)); + } + + writer.AppendLine("#endregion"); + writer.AppendLine(); + } + + // create the executor class + var classScope = writer.BeginScope($"public static class {resultName}"); + + writer.AppendLine($"public static readonly string Query = @\"{edgeql}\";"); + writer.AppendLine(); + var method = parseResult.Cardinality switch + { + Cardinality.AtMostOne => "QuerySingleAsync", + Cardinality.One => "QueryRequiredSingleAsync", + _ => "QueryAsync" + }; + + var resultType = parseResult.Cardinality switch + { + Cardinality.AtMostOne => $"{typeName ?? mainResult}?", + Cardinality.One => typeName ?? mainResult, + _ => $"IReadOnlyCollection<{typeName ?? mainResult}?>" + }; + + // build args + IEnumerable? argParameters; + IEnumerable? methodArgs; + if (parseResult.InCodec.Codec is NullCodec) + { + methodArgs = Array.Empty(); + argParameters = Array.Empty(); + } + else if (parseResult.InCodec.Codec is Codecs.Object argCodec) + { + argParameters = argParameters = argCodec.PropertyNames.Select((x, i) => BuildTypes(GetTypeOfCodec(argCodec.InnerCodecs[i], x), out _, namesOnScalar: true, camelCase: true)); + methodArgs = methodArgs = argCodec.PropertyNames.Select((x, i) => + { + return $"{{ \"{x}\", {TextUtils.ToCamelCase(x)} }}"; + }); + } + else + throw new InvalidOperationException("Argument codec is malformed"); + + writer.AppendLine($"public static Task<{resultType}> ExecuteAsync(IEdgeDBQueryable client{(argParameters.Any() ? $", {string.Join(", ", argParameters)}" : "")}, CancellationToken token = default)"); + writer.AppendLine($" => client.{method}<{typeName ?? mainResult}>(Query{(methodArgs.Any() ? $", new Dictionary() {{ {string.Join(", ", methodArgs)} }}" : "")}, capabilities: (Capabilities){(ulong)parseResult.Capabilities}ul, token: token);"); + + writer.AppendLine(); + writer.AppendLine($"public static Task<{resultType}> {resultName}Async(this IEdgeDBQueryable client{(argParameters.Any() ? $", {string.Join(", ", argParameters)}" : "")}, CancellationToken token = default)"); + writer.AppendLine($" => ExecuteAsync(client{(argParameters.Any() ? $", {string.Join(", ", argParameters.Select(x => x.Split(' ')[1]))}" : "")}, token: token);"); + + classScope.Dispose(); + + writer.AppendLine("#nullable restore"); + + return new() + { + ExecuterClassName = resultName, + EdgeQLHash = hash, + ReturnResult = resultType, + Parameters = argParameters, + Code = writer.ToString() + }; + } + + private static string BuildTypes(CodecTypeInfo info, out string? resultTypeName, List? usedObjects = null, bool namesOnScalar = false, bool camelCase = false, bool returnTypeName = false) + { + usedObjects ??= new(); + var writer = new CodeWriter(); + resultTypeName = null; + + var fmtName = info.Name is not null + ? camelCase + ? TextUtils.ToCamelCase(info.Name) + : TextUtils.ToPascalCase(info.Name) + : null; + + if (info.IsObject) + { + if (returnTypeName) + { + // add to used objects + usedObjects.Add(info); + if (namesOnScalar) + return $"{info.GetUniqueTypeName()}? {fmtName}"; + return fmtName!; + } + + // create the main class + writer.AppendLine("[EdgeDBType]"); + writer.AppendLine($"public sealed class {info.GetUniqueTypeName()}"); + using (_ = writer.BeginScope()) + { + var properties = info.Children!.Select(x => + { + var result = BuildTypes(x, out _, usedObjects, namesOnScalar: true, returnTypeName: true); + return $"[EdgeDBProperty(\"{x.Name}\")]{Environment.NewLine} public {result} {{ get; set; }}"; + }); + + writer.AppendLine(string.Join($"{Environment.NewLine}{Environment.NewLine} ", properties)); + } + + resultTypeName = info.TypeName!; + return writer.ToString(); + } + + if (info.IsTuple) + { + var types = info.Children!.Select(x => BuildTypes(x, out _, usedObjects, true)); + return $"({string.Join(", ", types)}){(namesOnScalar ? $" {fmtName}" : "")}"; + } + + if (info.IsArray) + { + var result = BuildTypes(info.Children!.Single(), out _, usedObjects, true); + return $"{result}[]{(namesOnScalar ? $" {fmtName}" : "")}"; + } + + if (info.IsSet) + { + var result = BuildTypes(info.Children!.Single(), out _, usedObjects, true); + return $"IEnumerable<{result}>{(namesOnScalar ? $" {fmtName}" : "")}"; + } + + + if (info.TypeName is not null) + return $"{info.TypeName}{(namesOnScalar ? $" {fmtName}" : "")}"; + + throw new InvalidOperationException($"Unknown type def {info}"); + } + + private static CodecTypeInfo GetTypeOfCodec(ICodec codec, string? name = null, CodecTypeInfo? parent = null) + { + // TODO: complete codec parsing + + CodecTypeInfo info; + + switch (codec) + { + case Codecs.Object obj: + { + info = new CodecTypeInfo + { + IsObject = true, + TypeName = TextUtils.ToPascalCase(name!) + }; + info.Children = obj.InnerCodecs + .Select((x, i) => + obj.PropertyNames[i] is "__tname__" or "__tid__" + ? null + : GetTypeOfCodec(x, obj.PropertyNames[i], info)) + .Where(x => x is not null)!; + } + break; + case ICodec set when ReflectionUtils.IsSubclassOfRawGeneric(typeof(Set<>), set.GetType()): + { + info = new CodecTypeInfo + { + IsArray = true, + }; + info.Children = new[] + { + GetTypeOfCodec((ICodec)set.GetType().GetField("_innerCodec", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(set)!, parent: info) + }; + } + break; + case ICodec array when ReflectionUtils.IsSubclassOfRawGeneric(typeof(Array<>), array.GetType()): + { + info = new CodecTypeInfo + { + IsSet = true, + }; + info.Children = new[] + { + GetTypeOfCodec((ICodec)array.GetType().GetField("_innerCodec", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(array)!, parent: info) + }; + } + break; + case Codecs.Tuple tuple: + { + info = new CodecTypeInfo + { + IsTuple = true, + }; + info.Children = tuple.InnerCodecs.Select(x => GetTypeOfCodec(x, parent: info)); + } + break; + case ICodec scalar when ReflectionUtils.IsSubclassOfInterfaceGeneric(typeof(IScalarCodec<>), codec!.GetType()): + { + info = new CodecTypeInfo + { + TypeName = $"{codec.GetType().GetInterface("IScalarCodec`1")!.GetGenericArguments()[0].Name}{(codec.GetType().GetInterface("IScalarCodec`1")!.GetGenericArguments()[0].IsValueType ? "" : "?")}", + }; + } + break; + default: + throw new InvalidOperationException($"Unknown codec {codec}"); + } + + info.Name = name ?? info.Name; + info.Parent = parent; + + return info; + } + + private class CodecTypeInfo + { + public bool IsArray { get; set; } + public bool IsSet { get; set; } + public bool IsObject { get; set; } + public bool IsTuple { get; set; } + public string? Name { get; set; } + public string? TypeName { get; set; } + public IEnumerable? Children { get; set; } + public CodecTypeInfo? Parent { get; set; } + + public bool BodyEquals(CodecTypeInfo info) + { + return IsArray == info.IsArray && + IsSet == info.IsSet && + IsObject == info.IsObject && + IsTuple == info.IsTuple && + (info.Children?.SequenceEqual(Children ?? Array.Empty()) ?? false); + } + + public string GetUniqueTypeName() + { + List path = new() { TypeName }; + var p = Parent; + while (p is not null) + { + path.Add(p.TypeName); + p = p.Parent; + } + path.Reverse(); + return string.Join("", path.Where(x => x is not null)); + } + + public override string ToString() + { + return $"{Name} ({TypeName})"; + } + } + } +} diff --git a/src/EdgeDB.Net.CLI/FrameworkWriters/CSharpLanguageWriter.cs b/src/EdgeDB.Net.CLI/FrameworkWriters/CSharpLanguageWriter.cs deleted file mode 100644 index d7f2dcee..00000000 --- a/src/EdgeDB.Net.CLI/FrameworkWriters/CSharpLanguageWriter.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.CLI; - -internal class CSharpLanguageWriter : ILanguageWriter -{ -} diff --git a/src/EdgeDB.Net.CLI/FrameworkWriters/ILanguageWriter.cs b/src/EdgeDB.Net.CLI/FrameworkWriters/ILanguageWriter.cs deleted file mode 100644 index 3d6a872b..00000000 --- a/src/EdgeDB.Net.CLI/FrameworkWriters/ILanguageWriter.cs +++ /dev/null @@ -1,18 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.CLI; - -internal interface ILanguageWriter -{ - public static ILanguageWriter GetWriter(string lang) - { - return lang switch - { - "c#" => new CSharpLanguageWriter() - }; - } -} diff --git a/src/EdgeDB.Net.CLI/Program.cs b/src/EdgeDB.Net.CLI/Program.cs index 39284564..33882da9 100644 --- a/src/EdgeDB.Net.CLI/Program.cs +++ b/src/EdgeDB.Net.CLI/Program.cs @@ -1,5 +1,4 @@ -// load commands -using CommandLine; +using CommandLine; using CommandLine.Text; using EdgeDB.CLI; @@ -12,15 +11,30 @@ var result = parser.ParseArguments(args, commands.ToArray()); -var commandResult = await result.WithParsedAsync(x => x.ExecuteAsync()); +try +{ + var commandResult = await result.WithParsedAsync(x => x.ExecuteAsync()); + + + result.WithNotParsed(err => + { + var helpText = HelpText.AutoBuild(commandResult, h => + { + h.AdditionalNewLineAfterOption = true; + h.Heading = "EdgeDB.Net CLI"; + h.Copyright = "Copyright (c) 2022 EdgeDB"; -var helpText = HelpText.AutoBuild(commandResult, h => + return h; + }, e => e, verbsIndex: true); + + Console.WriteLine(helpText); + }); + +} +catch (Exception x) { - h.AdditionalNewLineAfterOption = true; - h.Heading = "EdgeDB.Net CLI"; - h.Copyright = "Copyright (c) 2022 EdgeDB"; + Console.WriteLine(x); +} + - return h; -}, e => e, verbsIndex: true); -Console.WriteLine(helpText); diff --git a/src/EdgeDB.Net.CLI/Properties/launchSettings.json b/src/EdgeDB.Net.CLI/Properties/launchSettings.json index 08a62d0f..3ea913d2 100644 --- a/src/EdgeDB.Net.CLI/Properties/launchSettings.json +++ b/src/EdgeDB.Net.CLI/Properties/launchSettings.json @@ -2,7 +2,7 @@ "profiles": { "EdgeDB.Net.CLI": { "commandName": "Project", - "commandLineArgs": "generate" + "commandLineArgs": "generate -f" } } } \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs b/src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs new file mode 100644 index 00000000..8085d0e4 --- /dev/null +++ b/src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs @@ -0,0 +1,455 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace EdgeDB.CLI.Utils +{ + internal class EdgeDBColorer + { + public const int ANSI_COLOR_RED = 31; + public const int ANSI_COLOR_GREEN = 32; + public const int ANSI_COLOR_YELLOW = 33; + public const int ANSI_COLOR_BLUE = 34; + public const int ANSI_COLOR_PINK = 35; + public const int ANSI_COLOR_WHITE = 37; + + public static string ColorSchemaOrQuery(string edgeql, Range ignore) + { + var lIndent = 0; + var left = edgeql[..ignore.Start]; + var right = edgeql[ignore.End..]; + var unmarkedContent = edgeql[ignore]; + return ColorSchemaOrQuery(left, ref lIndent) + unmarkedContent + ColorSchemaOrQuery(right, ref lIndent); + } + + public static string ColorSchemaOrQuery(string edgeql) + { + int i = 0; + return ColorSchemaOrQuery(edgeql, ref i); + } + + public static string ColorSchemaOrQuery(string edgeql, ref int indentLevel) + { + var mutableSchema = edgeql; + var coloredSchema = ""; + var currentColor = 0; + + void ChangeColor(int color) + { + if (currentColor == color) + return; + + coloredSchema += $"\u001b[0;{color}m"; + currentColor = color; + } + + while (!string.IsNullOrEmpty(mutableSchema)) + { + var word = new string(mutableSchema.TakeWhile(x => !(x is '<' or '>' or '\"' or '\'' or ',' or '{' or '}' or '[' or ']' or ' ' or '(' or ')' or ';' or ':')).ToArray()); + + if (string.IsNullOrEmpty(word)) + word = $"{mutableSchema.FirstOrDefault('\u0000')}"; + + if (word == "\u0000") + break; + + mutableSchema = mutableSchema[(word.Length)..]; + + var lower = word.ToLower(); + + if(Regex.IsMatch(word, @"^\d+$")) + ChangeColor(ANSI_COLOR_GREEN); + else if (word == "<") + { + int d = 0; + var str = new string(mutableSchema.TakeWhile((c, i) => + { + if (c == '<') + d++; + else if (c == '>' && d == 0) + return false; + else if (c == '>') + d--; + return true; + + }).ToArray()); + word += str + mutableSchema[str.Length]; + ChangeColor(ANSI_COLOR_RED); + mutableSchema = mutableSchema[(word.Length - 1)..]; + } + else if (word == "\"") + { + var str = new string(mutableSchema.TakeWhile((c, i) => !(c is '\"' && mutableSchema[i - 1] is not '\\')).ToArray()); + word += str + mutableSchema[str.Length]; + ChangeColor(ANSI_COLOR_YELLOW); + mutableSchema = mutableSchema[(word.Length - 1)..]; + } + else if (word == "\'") + { + var str = new string(mutableSchema.TakeWhile((c, i) => !(c is '\'' && mutableSchema[i - 1] is not '\\')).ToArray()); + word += str + mutableSchema[str.Length]; + ChangeColor(ANSI_COLOR_YELLOW); + mutableSchema = mutableSchema[(word.Length - 1)..]; + } + else if (word == " " || word == "\n") { } + else if (word is "{" or "}" or "(" or ")") + { + if (word is "{" or "(") + indentLevel++; + + switch (indentLevel % 3) + { + case 1: + ChangeColor(ANSI_COLOR_YELLOW); + break; + case 2: + ChangeColor(ANSI_COLOR_PINK); + break; + default: + ChangeColor(ANSI_COLOR_BLUE); + break; + } + + if (word is "}" or ")") + indentLevel--; + } + else if (word is ")" or "(") + ChangeColor(ANSI_COLOR_BLUE); + else if (ReservedKeywords.Contains(lower) || Unreservedkeywords.Contains(lower) || BoolLiterals.Contains(lower)) + ChangeColor(ANSI_COLOR_BLUE); + else if (TypesBuiltIns.Contains(lower)) + ChangeColor(ANSI_COLOR_GREEN); + else if (ConstraintsBuiltIns.Contains(lower) || FunctionsBuiltIns.Contains(lower)) + ChangeColor(ANSI_COLOR_YELLOW); + else + ChangeColor(ANSI_COLOR_WHITE); + + coloredSchema += $"{word}"; + } + + return coloredSchema; + } + + private static string[] ReservedKeywords = new string[] + { + "__source__", + "__std__", + "__subject__", + "__type__", + "abort", + "alter", + "analyze", + "and", + "anyarray", + "anytuple", + "anytype", + "begin", + "case", + "check", + "commit", + "configure", + "constraint", + "create", + "deallocate", + "declare", + "delete", + "describe", + "detached", + "discard", + "distinct", + "do", + "drop", + "else", + "empty", + "end", + "execute", + "exists", + "explain", + "extending", + "fetch", + "filter", + "for", + "get", + "global", + "grant", + "group", + "if", + "ilike", + "import", + "in", + "insert", + "introspect", + "is", + "like", + "limit", + "listen", + "load", + "lock", + "match", + "module", + "move", + "not", + "notify", + "offset", + "optional", + "or", + "order", + "over", + "partition", + "policy", + "populate", + "prepare", + "raise", + "refresh", + "reindex", + "release", + "reset", + "revoke", + "rollback", + "select", + "set", + "start", + "typeof", + "union", + "update", + "variadic", + "when", + "window", + "with", + }; + + private static string[] Unreservedkeywords = new string[] + { + "abstract", + "after", + "alias", + "all", + "allow", + "annotation", + "as", + "asc", + "assignment", + "before", + "by", + "cardinality", + "cast", + "config", + "conflict", + "constraint", + "current", + "database", + "ddl", + "default", + "deferrable", + "deferred", + "delegated", + "desc", + "emit", + "explicit", + "expression", + "final", + "first", + "from", + "function", + "implicit", + "index", + "infix", + "inheritable", + "into", + "isolation", + "json", + "last", + "link", + "migration", + "multi", + "named", + "object", + "of", + "oids", + "on", + "only", + "onto", + "operator", + "overloaded", + "owned", + "postfix", + "prefix", + "property", + "proposed", + "pseudo", + "read", + "reject", + "rename", + "repeatable", + "required", + "restrict", + "role", + "roles", + "savepoint", + "scalar", + "schema", + "sdl", + "serializable", + "session", + "single", + "source", + "superuser", + "system", + "target", + "ternary", + "text", + "then", + "to", + "transaction", + "type", + "unless", + "using", + "verbose", + "view", + "write", + }; + + private static string[] BoolLiterals = new string[] + { + "false", + "true", + }; + + private static string[] TypesBuiltIns = new string[] + { + "BaseObject", + "Object", + "anyenum", + "anyfloat", + "anyint", + "anynumeric", + "anyreal", + "anyscalar", + "array", + "bigint", + "bool", + "bytes", + "datetime", + "decimal", + "duration", + "enum", + "float32", + "float64", + "int16", + "int32", + "int64", + "json", + "local_date", + "local_datetime", + "local_time", + "sequence", + "str", + "tuple", + "uuid", + }; + + private static string[] ConstraintsBuiltIns = new string[] + { + "exclusive", + "expression", + "len_value", + "max_ex_value", + "max_len_value", + "max_value", + "min_ex_value", + "min_len_value", + "min_value", + "one_of", + "regexp", + }; + + private static string[] FunctionsBuiltIns = new string[] + { + "abs", + "advisory_lock", + "advisory_unlock", + "advisory_unlock_all", + "all", + "any", + "array_agg", + "array_get", + "array_join", + "array_unpack", + "bytes_get_bit", + "ceil", + "contains", + "count", + "date_get", + "datetime_current", + "datetime_get", + "datetime_of_statement", + "datetime_of_transaction", + "datetime_truncate", + "duration_to_seconds", + "duration_truncate", + "enumerate", + "find", + "floor", + "get_current_database", + "get_transaction_isolation", + "get_version", + "get_version_as_str", + "json_array_unpack", + "json_get", + "json_object_unpack", + "json_typeof", + "len", + "lg", + "ln", + "log", + "max", + "mean", + "min", + "random", + "re_match", + "re_match_all", + "re_replace", + "re_test", + "round", + "sleep", + "stddev", + "stddev_pop", + "str_lower", + "str_lpad", + "str_ltrim", + "str_pad_end", + "str_pad_start", + "str_repeat", + "str_rpad", + "str_rtrim", + "str_split", + "str_title", + "str_trim", + "str_trim_end", + "str_trim_start", + "str_upper", + "sum", + "time_get", + "to_bigint", + "to_datetime", + "to_decimal", + "to_duration", + "to_float32", + "to_float64", + "to_int16", + "to_int32", + "to_int64", + "to_json", + "to_local_date", + "to_local_datetime", + "to_local_time", + "to_str", + "uuid_generate_v1mc", + "var", + "var_pop", + }; + } +} diff --git a/src/EdgeDB.Net.CLI/Utils/HashUtils.cs b/src/EdgeDB.Net.CLI/Utils/HashUtils.cs new file mode 100644 index 00000000..1d6a65df --- /dev/null +++ b/src/EdgeDB.Net.CLI/Utils/HashUtils.cs @@ -0,0 +1,19 @@ +using EdgeDB.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI.Utils +{ + internal class HashUtils + { + public static string HashEdgeQL(string edgeql) + { + using var algo = SHA256.Create(); + return HexConverter.ToHex(algo.ComputeHash(Encoding.UTF8.GetBytes(edgeql))); + } + } +} diff --git a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs index 57cb650d..2711d3fd 100644 --- a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs @@ -25,22 +25,30 @@ public static string GetProjectRoot() return directory; } - public static async Task CreateGeneratedProjectAsync(string root, string name, string language) + public static async Task CreateGeneratedProjectAsync(string root, string name) { var result = await Cli.Wrap("dotnet") - .WithArguments($"new classlib --framework \"net6.0\" -o {name} -lang {language}") + .WithArguments($"new classlib --framework \"net6.0\" -o {name}") .WithWorkingDirectory(root) .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) .ExecuteAsync(); + if (result.ExitCode != 0) + throw new IOException($"Failed to create new project"); + + result = await Cli.Wrap("dotnet") + .WithArguments("add package EdgeDB.Net.Driver") + .WithWorkingDirectory(Path.Combine(root, name)) + .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) + .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) + .ExecuteAsync(); + + if (result.ExitCode != 0) + throw new IOException($"Failed to create new project"); + // remove default file - File.Delete(Path.Combine(root, name, language switch - { - "c#" => "Class1.cs", - "vb" => "Class1.vb", - "f#" => "Library.fs", - })); + File.Delete(Path.Combine(root, name, "Class1.cs")); } public static IEnumerable GetTargetEdgeQLFiles(string root) diff --git a/src/EdgeDB.Net.CLI/Utils/QueryErrorFormatter.cs b/src/EdgeDB.Net.CLI/Utils/QueryErrorFormatter.cs new file mode 100644 index 00000000..6b21dda6 --- /dev/null +++ b/src/EdgeDB.Net.CLI/Utils/QueryErrorFormatter.cs @@ -0,0 +1,37 @@ +using EdgeDB; +using EdgeDB.Binary; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI.Utils +{ + internal class QueryErrorFormatter + { + public static string FormatError(string query, EdgeDBErrorException error) + { + var headers = error.ErrorResponse.Attributes.Cast(); + var rawCharStart = headers.FirstOrDefault(x => x.HasValue && x.Value.Code == 0xFFF9, null); + var rawCharEnd = headers.FirstOrDefault(x => x.HasValue && x.Value.Code == 0xFFFA, null); + + if (!rawCharStart.HasValue || !rawCharEnd.HasValue) + return EdgeDBColorer.ColorSchemaOrQuery(query); + + int charStart = int.Parse(rawCharStart.Value.ToString()), + charEnd = int.Parse(rawCharEnd.Value.ToString()); + + var queryErrorSource = query[charStart..charEnd]; + var count = charEnd - charStart; + + var coloredQuery = EdgeDBColorer.ColorSchemaOrQuery(query, charStart..charEnd); + + // make the error section red + var coloredIndex = coloredQuery.IndexOf(queryErrorSource, charStart); + coloredQuery = coloredQuery.Remove(coloredIndex, count); + coloredQuery = coloredQuery.Insert(coloredIndex, $"\u001b[0;31m{queryErrorSource}"); + return coloredQuery + "\u001b[0m"; + } + } +} diff --git a/src/EdgeDB.Net.CLI/Utils/TextUtils.cs b/src/EdgeDB.Net.CLI/Utils/TextUtils.cs new file mode 100644 index 00000000..7b6cb9c1 --- /dev/null +++ b/src/EdgeDB.Net.CLI/Utils/TextUtils.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI.Utils +{ + internal class TextUtils + { + private static CultureInfo? _cultureInfo; + + public static string ToPascalCase(string input) + { + _cultureInfo ??= CultureInfo.CurrentCulture; + + return _cultureInfo.TextInfo.ToTitleCase(input.Replace("_", " ")).Replace(" ", ""); + } + + public static string ToCamelCase(string input) + { + var p = ToPascalCase(input); + return $"{p[0].ToString().ToLower()}{p[1..]}"; + } + } +} diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index 6720e8de..7f60fac6 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -159,12 +159,17 @@ internal readonly struct ParseResult public readonly CodecInfo InCodec; public readonly CodecInfo OutCodec; public readonly IDictionary State; + public readonly Cardinality Cardinality; + public readonly Capabilities Capabilities; - public ParseResult(CodecInfo inCodec, CodecInfo outCodec, IDictionary state) + public ParseResult(CodecInfo inCodec, CodecInfo outCodec, IDictionary state, + Cardinality cardinality, Capabilities capabilities) { InCodec = inCodec; OutCodec = outCodec; State = state; + Cardinality = cardinality; + Capabilities = capabilities; } } @@ -307,6 +312,9 @@ bool parseHandlerPredicate(IReceiveable? packet) CodecBuilder.BuildCodec(descriptor.InputTypeDescriptorId, descriptor.InputTypeDescriptorBuffer)); CodecBuilder.UpdateKeyMap(cacheKey, descriptor.InputTypeDescriptorId, descriptor.OutputTypeDescriptorId); + + cardinality = descriptor.Cardinality; + capabilities = descriptor.Capabilities; } break; case StateDataDescription stateDescriptor: @@ -348,7 +356,7 @@ bool parseHandlerPredicate(IReceiveable? packet) throw new MissingCodecException("Couldn't find a valid input codec"); } - return new ParseResult(inCodecInfo, outCodecInfo, serializedState); + return new ParseResult(inCodecInfo, outCodecInfo, serializedState, cardinality, capabilities ?? Capabilities.ReadOnly); } /// diff --git a/src/EdgeDB.Net.Driver/Codecs/Tuple.cs b/src/EdgeDB.Net.Driver/Codecs/Tuple.cs index feb33fd0..468d3962 100644 --- a/src/EdgeDB.Net.Driver/Codecs/Tuple.cs +++ b/src/EdgeDB.Net.Driver/Codecs/Tuple.cs @@ -5,20 +5,20 @@ namespace EdgeDB.Codecs { internal class Tuple : ICodec { - private readonly ICodec[] _innerCodecs; + internal readonly ICodec[] InnerCodecs; public Tuple(ICodec[] innerCodecs) { - _innerCodecs = innerCodecs; + InnerCodecs = innerCodecs; } public TransientTuple Deserialize(ref PacketReader reader) { var numElements = reader.ReadInt32(); - if(_innerCodecs.Length != numElements) + if(InnerCodecs.Length != numElements) { - throw new ArgumentException($"codecs mismatch for tuple: expected {numElements} codecs, got {_innerCodecs.Length} codecs"); + throw new ArgumentException($"codecs mismatch for tuple: expected {numElements} codecs, got {InnerCodecs.Length} codecs"); } // deserialize our values @@ -41,10 +41,10 @@ public TransientTuple Deserialize(ref PacketReader reader) reader.ReadBytes(length, out var data); var innerReader = new PacketReader(data); - values[i] = _innerCodecs[i].Deserialize(ref innerReader); + values[i] = InnerCodecs[i].Deserialize(ref innerReader); } - return new TransientTuple(_innerCodecs.Select(x => x.ConverterType).ToArray(), values); + return new TransientTuple(InnerCodecs.Select(x => x.ConverterType).ToArray(), values); } public void Serialize(PacketWriter writer, TransientTuple value) diff --git a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs index 708500aa..ba8e5746 100644 --- a/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs +++ b/src/EdgeDB.Net.Driver/Utils/ReflectionUtils.cs @@ -25,6 +25,12 @@ public static bool IsSubclassOfRawGeneric(Type generic, Type? toCheck) return false; } + public static bool IsSubclassOfInterfaceGeneric(Type generic, Type? toCheck) + { + var interfaces = toCheck!.GetInterfaces(); + return interfaces.Any(x => IsSubclassOfRawGeneric(generic, x)); + } + public static bool TryGetRawGeneric(Type generic, Type? toCheck, out Type? genericReference) { genericReference = null; diff --git a/test.edgeql b/test.edgeql deleted file mode 100644 index b5a09212..00000000 --- a/test.edgeql +++ /dev/null @@ -1,3 +0,0 @@ -select Person { - name, email -} filter .id = $user_id \ No newline at end of file From 0818c44aa6315a5e0595d263529238d03e3eece1 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Wed, 24 Aug 2022 12:53:23 -0300 Subject: [PATCH 04/13] Work on watch mode & demo project --- EdgeDB.Net.sln | 14 ++- .../EdgeDB.Examples.GenerationExample.csproj | 16 +++ .../EdgeDB.Generated/Class1.cs | 5 + .../EdgeDB.Generated/EdgeDB.Generated.csproj | 9 ++ .../Getuser.g.cs | 38 +++++++ .../Program.cs | 2 + .../Scrips/GetUser.edgeql | 4 + src/EdgeDB.Net.CLI/Commands/FileWatch.cs | 99 +++++++++++++++++++ src/EdgeDB.Net.CLI/Commands/Generate.cs | 41 +++----- src/EdgeDB.Net.CLI/EdgeQLParser.cs | 65 +++++++++--- .../Properties/launchSettings.json | 3 +- src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs | 6 +- .../Clients/EdgeDBBinaryClient.cs | 5 +- 13 files changed, 261 insertions(+), 46 deletions(-) create mode 100644 examples/EdgeDB.Examples.GenerationExample/EdgeDB.Examples.GenerationExample.csproj create mode 100644 examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/Class1.cs create mode 100644 examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/EdgeDB.Generated.csproj create mode 100644 examples/EdgeDB.Examples.GenerationExample/Getuser.g.cs create mode 100644 examples/EdgeDB.Examples.GenerationExample/Program.cs create mode 100644 examples/EdgeDB.Examples.GenerationExample/Scrips/GetUser.edgeql create mode 100644 src/EdgeDB.Net.CLI/Commands/FileWatch.cs diff --git a/EdgeDB.Net.sln b/EdgeDB.Net.sln index 9ede2048..e5d40fd9 100644 --- a/EdgeDB.Net.sln +++ b/EdgeDB.Net.sln @@ -35,7 +35,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Examples.ExampleTODO EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Net.CLI", "src\EdgeDB.Net.CLI\EdgeDB.Net.CLI.csproj", "{77D1980E-9835-4D16-B7B4-6CB5A4BD570C}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Generated", "EdgeDB.Generated\EdgeDB.Generated.csproj", "{A7F2328A-F2CE-43AB-99A3-41413BB735F6}" +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CLI Generation Example", "CLI Generation Example", "{46E94884-7A3D-4B48-9734-916ECCCB85C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdgeDB.Examples.GenerationExample", "examples\EdgeDB.Examples.GenerationExample\EdgeDB.Examples.GenerationExample.csproj", "{4BCAA352-8488-46A9-B3C9-D2C50E937AFC}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -87,10 +89,10 @@ Global {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Debug|Any CPU.Build.0 = Debug|Any CPU {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Release|Any CPU.ActiveCfg = Release|Any CPU {77D1980E-9835-4D16-B7B4-6CB5A4BD570C}.Release|Any CPU.Build.0 = Release|Any CPU - {A7F2328A-F2CE-43AB-99A3-41413BB735F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {A7F2328A-F2CE-43AB-99A3-41413BB735F6}.Debug|Any CPU.Build.0 = Debug|Any CPU - {A7F2328A-F2CE-43AB-99A3-41413BB735F6}.Release|Any CPU.ActiveCfg = Release|Any CPU - {A7F2328A-F2CE-43AB-99A3-41413BB735F6}.Release|Any CPU.Build.0 = Release|Any CPU + {4BCAA352-8488-46A9-B3C9-D2C50E937AFC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BCAA352-8488-46A9-B3C9-D2C50E937AFC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BCAA352-8488-46A9-B3C9-D2C50E937AFC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BCAA352-8488-46A9-B3C9-D2C50E937AFC}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -107,6 +109,8 @@ Global {6FA68DEA-D398-4A5B-8025-5F15C728F04C} = {49B6FB80-A675-4ECA-802C-2337A4F37566} {E38429C6-53A5-4311-8189-1F78238666DC} = {6FC214F5-C912-4D99-91B1-3E9F52A4E11B} {77D1980E-9835-4D16-B7B4-6CB5A4BD570C} = {025AAADF-16AF-4367-9C3D-9E60EDED832F} + {46E94884-7A3D-4B48-9734-916ECCCB85C0} = {6FC214F5-C912-4D99-91B1-3E9F52A4E11B} + {4BCAA352-8488-46A9-B3C9-D2C50E937AFC} = {46E94884-7A3D-4B48-9734-916ECCCB85C0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4E90C94F-D693-4411-82F3-2051DE1BE052} diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Examples.GenerationExample.csproj b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Examples.GenerationExample.csproj new file mode 100644 index 00000000..c95ba71e --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Examples.GenerationExample.csproj @@ -0,0 +1,16 @@ + + + + Exe + net6.0 + enable + enable + + + + + + + + + diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/Class1.cs b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/Class1.cs new file mode 100644 index 00000000..1dbe1e63 --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/Class1.cs @@ -0,0 +1,5 @@ +namespace EdgeDB.Generated; +public class Class1 +{ + +} diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/EdgeDB.Generated.csproj b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/EdgeDB.Generated.csproj new file mode 100644 index 00000000..132c02c5 --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/EdgeDB.Generated.csproj @@ -0,0 +1,9 @@ + + + + net6.0 + enable + enable + + + diff --git a/examples/EdgeDB.Examples.GenerationExample/Getuser.g.cs b/examples/EdgeDB.Examples.GenerationExample/Getuser.g.cs new file mode 100644 index 00000000..ff395861 --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/Getuser.g.cs @@ -0,0 +1,38 @@ +// AUTOGENERATED: DO NOT MODIFY +// edgeql:E116C5881529C70BFC6A9D0350075968AD967D391FEE0F74033FA870A1FE56DD +// Generated on 2022-08-24T13:50:21.2137406Z +#nullable enable +using EdgeDB; + +namespace EdgeDB.Generated; + +#region Types +[EdgeDBType] +public sealed class GetuserResult +{ + [EdgeDBProperty("id")] + public Guid Id { get; set; } + + [EdgeDBProperty("name")] + public String? Name { get; set; } + + [EdgeDBProperty("email")] + public String? Email { get; set; } +} + +#endregion + +public static class Getuser +{ + public static readonly string Query = @"select Person { + name, email +} +filter .email = $email"; + + public static Task ExecuteAsync(IEdgeDBQueryable client, String? email, CancellationToken token = default) + => client.QuerySingleAsync(Query, new Dictionary() { { "email", email } }, capabilities: (Capabilities)0ul, token: token); + + public static Task GetuserAsync(this IEdgeDBQueryable client, String? email, CancellationToken token = default) + => ExecuteAsync(client, email, token: token); +} +#nullable restore diff --git a/examples/EdgeDB.Examples.GenerationExample/Program.cs b/examples/EdgeDB.Examples.GenerationExample/Program.cs new file mode 100644 index 00000000..3751555c --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/Program.cs @@ -0,0 +1,2 @@ +// See https://aka.ms/new-console-template for more information +Console.WriteLine("Hello, World!"); diff --git a/examples/EdgeDB.Examples.GenerationExample/Scrips/GetUser.edgeql b/examples/EdgeDB.Examples.GenerationExample/Scrips/GetUser.edgeql new file mode 100644 index 00000000..2d4f61b6 --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/Scrips/GetUser.edgeql @@ -0,0 +1,4 @@ +select Person { + name, email +} +filter .email = $email \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs new file mode 100644 index 00000000..7ee53009 --- /dev/null +++ b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs @@ -0,0 +1,99 @@ +using CommandLine; +using EdgeDB.CLI; +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Net.CLI.Commands +{ + [Verb("file-watch-internal", Hidden = true)] + internal class FileWatch : ICommand + { + [Option("connection")] + public string? Connection { get; set; } + + [Option("dir")] + public string? Dir { get; set; } + + [Option("namespace")] + public string? Namespace { get; set; } + + private readonly FileSystemWatcher _watcher = new(); + private readonly EdgeDBTcpClient _client; + public FileWatch() + { + if (Connection is null) + throw new InvalidOperationException("Connection must be specified"); + + _client = new(JsonConvert.DeserializeObject(Connection)!, new()); + } + + public async Task ExecuteAsync() + { + _watcher.Path = Dir; + _watcher.Filter = "*.edgeql"; + _watcher.IncludeSubdirectories = true; + + _watcher.Error += _watcher_Error; + _watcher.Changed += _watcher_Changed; + _watcher.Created += _watcher_Created; + _watcher.Deleted += _watcher_Deleted; + _watcher.Renamed += _watcher_Renamed; + + _watcher.NotifyFilter = NotifyFilters.Attributes + | NotifyFilters.CreationTime + | NotifyFilters.DirectoryName + | NotifyFilters.FileName + | NotifyFilters.LastAccess + | NotifyFilters.LastWrite + | NotifyFilters.Security + | NotifyFilters.Size; + + _watcher.EnableRaisingEvents = true; + + await Task.Delay(-1); + } + + public async Task GenerateAsync(EdgeQLParser.GenerationTargetInfo info) + { + await _client.ConnectAsync(); + + var result = await EdgeQLParser.ParseAndGenerateAsync(_client, Namespace!, info); + + File.WriteAllText(info.TargetFilePath!, result.Code); + } + + private void _watcher_Created(object sender, FileSystemEventArgs e) + { + var info = EdgeQLParser.GetTargetInfo(e.FullPath, Dir!); + + if (info.GeneratedTargetExistsAndIsUpToDate()) + return; + + Task.Run(async () => await GenerateAsync(info)); + } + + private void _watcher_Changed(object sender, FileSystemEventArgs e) + { + throw new NotImplementedException(); + } + + private void _watcher_Deleted(object sender, FileSystemEventArgs e) + { + throw new NotImplementedException(); + } + + private void _watcher_Renamed(object sender, RenamedEventArgs e) + { + throw new NotImplementedException(); + } + + private void _watcher_Error(object sender, ErrorEventArgs e) + { + Console.Error.WriteLine($"An error occored: {e.GetException()}"); + } + } +} diff --git a/src/EdgeDB.Net.CLI/Commands/Generate.cs b/src/EdgeDB.Net.CLI/Commands/Generate.cs index 06e8b497..660c5a20 100644 --- a/src/EdgeDB.Net.CLI/Commands/Generate.cs +++ b/src/EdgeDB.Net.CLI/Commands/Generate.cs @@ -16,7 +16,7 @@ public class Generate : ConnectionArguments, ICommand [Option('o', "output", HelpText = "The output directory for the generated source to be placed.")] public string? OutputDirectory { get; set; } - [Option('n', "generated-project-name", HelpText = "The name of the generated project.")] + [Option('n', "project-name", HelpText = "The name of the generated project.")] public string GeneratedProjectName { get; set; } = "EdgeDB.Generated"; [Option('f', "force", HelpText = "Force regeneration of files")] @@ -35,57 +35,48 @@ public async Task ExecuteAsync() var projectRoot = ProjectUtils.GetProjectRoot(); - OutputDirectory ??= Path.Combine(projectRoot, GeneratedProjectName); + OutputDirectory ??= projectRoot; - if (GenerateProject && !Directory.Exists(OutputDirectory)) + Directory.CreateDirectory(OutputDirectory); + + if (GenerateProject && !Directory.Exists(Path.Combine(OutputDirectory, GeneratedProjectName))) { Console.WriteLine($"Creating project {GeneratedProjectName}..."); - await ProjectUtils.CreateGeneratedProjectAsync(projectRoot, GeneratedProjectName); + await ProjectUtils.CreateGeneratedProjectAsync(OutputDirectory, GeneratedProjectName); + OutputDirectory = Path.Combine(OutputDirectory, GeneratedProjectName); } // find edgeql files var edgeqlFiles = ProjectUtils.GetTargetEdgeQLFiles(projectRoot).ToArray(); - - // compute the hashes for each file - string[] hashs = edgeqlFiles.Select(x => HashUtils.HashEdgeQL(File.ReadAllText(x))).ToArray(); - + Console.WriteLine($"Generating {edgeqlFiles.Length} files..."); for(int i = 0; i != edgeqlFiles.Length; i++) { var file = edgeqlFiles[i]; - var hash = hashs[i]; - var targetFileName = TextUtils.ToPascalCase(Path.GetFileName(file).Split('.')[0]); - var targetOutputPath = Path.Combine(projectRoot, GeneratedProjectName, $"{targetFileName}.g.cs"); - var edgeql = File.ReadAllText(file); + var info = EdgeQLParser.GetTargetInfo(file, OutputDirectory); - if (!Force && File.Exists(targetOutputPath)) + if (!Force && info.GeneratedTargetExistsAndIsUpToDate()) { - // check the hashes - var hashHeader = Regex.Match(File.ReadAllLines(targetOutputPath)[1], @"\/\/ edgeql:([0-9a-fA-F]{64})"); - - if(hashHeader.Success && hashHeader.Groups[1].Value == hash) - { - Console.WriteLine($"{i + 1}: Skipping {file}: File already generated"); - continue; - } + Console.WriteLine($"{i + 1}: Skipping {file}: File already generated and up-to-date."); + continue; } try { - var result = await EdgeQLParser.ParseAndGenerateAsync(client, edgeql, GeneratedProjectName, hash, targetFileName); - File.WriteAllText(targetOutputPath, result.Code); + var result = await EdgeQLParser.ParseAndGenerateAsync(client, GeneratedProjectName, info); + File.WriteAllText(info.TargetFilePath!, result.Code); } catch (EdgeDBErrorException error) { Console.WriteLine($"Failed to parse {file} (line {error.ErrorResponse.Attributes.FirstOrDefault(x => x.Code == 65523).ToString() ?? "??"}, column {error.ErrorResponse.Attributes.FirstOrDefault(x => x.Code == 65524).ToString() ?? "??"}):"); Console.WriteLine(error.Message); - Console.WriteLine(QueryErrorFormatter.FormatError(edgeql, error)); + Console.WriteLine(QueryErrorFormatter.FormatError(info.EdgeQL!, error)); Console.WriteLine($"{i + 1}: Skipping {file}: File contains errors"); continue; } - Console.WriteLine($"{i + 1}: {file} => {targetOutputPath}"); + Console.WriteLine($"{i + 1}: {file} => {info.TargetFilePath}"); } Console.WriteLine("Generation complete!"); diff --git a/src/EdgeDB.Net.CLI/EdgeQLParser.cs b/src/EdgeDB.Net.CLI/EdgeQLParser.cs index 8f5be5da..d14fd1ae 100644 --- a/src/EdgeDB.Net.CLI/EdgeQLParser.cs +++ b/src/EdgeDB.Net.CLI/EdgeQLParser.cs @@ -5,17 +5,60 @@ using System.Linq; using System.Reflection; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace EdgeDB.CLI { internal class EdgeQLParser { - public static async Task ParseAndGenerateAsync(EdgeDBTcpClient client, string edgeql, string @namespace, string hash, string name) + private static readonly Regex _headerHashRegex = new(@"\/\/ edgeql:([0-9a-fA-F]{64})"); + + public static async Task ParseAndGenerateAsync(EdgeDBTcpClient client, string @namespace, GenerationTargetInfo targetInfo) + { + var parseResult = await client.ParseAsync(targetInfo.EdgeQL!, Cardinality.Many, IOFormat.Binary, Capabilities.All, default); + + return GenerateCSharpFromEdgeQL(@namespace, targetInfo, parseResult); + } + + public static bool TargetFileHashMatches(string header, string hash) + { + var match = _headerHashRegex.Match(header); + if (!match.Success) + return false; + return match.Groups[1].Value == hash; + } + + public static GenerationTargetInfo GetTargetInfo(string edgeqlFile, string targetDir) + { + var fileContent = File.ReadAllText(edgeqlFile); + var hash = HashUtils.HashEdgeQL(fileContent); + var fileName = TextUtils.ToPascalCase(Path.GetFileName(edgeqlFile).Split('.')[0]); + + return new GenerationTargetInfo + { + FileNameWithoutExtension = fileName, + EdgeQL = fileContent, + EdgeQLHash = hash, + EdgeQLFilePath = edgeqlFile, + TargetFilePath = Path.Combine(targetDir, $"{fileName}.g.cs") + }; + } + + public class GenerationTargetInfo { - var parseResult = await client.ParseAsync(edgeql, Cardinality.Many, IOFormat.Binary, Capabilities.All, default); + public string? FileNameWithoutExtension { get; set; } + public string? EdgeQLFilePath { get; set; } + public string? TargetFilePath { get; set; } + public string? EdgeQL { get; set; } + public string? EdgeQLHash { get; set; } + + public bool GeneratedTargetExistsAndIsUpToDate() + { + var lines = File.Exists(TargetFilePath) ? File.ReadAllLines(TargetFilePath) : Array.Empty(); - return GenerateCSharpFromEdgeQL(@namespace, edgeql, hash, name, parseResult); + return File.Exists(TargetFilePath) && lines.Length >= 2 && TargetFileHashMatches(lines[1], EdgeQLHash!); + } } public class GenerationResult @@ -27,15 +70,15 @@ public class GenerationResult public IEnumerable? Parameters { get; set; } } - private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, string edgeql, string hash, string resultName, EdgeDBBinaryClient.ParseResult parseResult) + private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, GenerationTargetInfo targetInfo, EdgeDBBinaryClient.ParseResult parseResult) { - var codecType = GetTypeOfCodec(parseResult.OutCodec.Codec, $"{resultName} Result"); + var codecType = GetTypeOfCodec(parseResult.OutCodec.Codec, $"{targetInfo.FileNameWithoutExtension} Result"); // create the class writer var writer = new CodeWriter(); writer.AppendLine("// AUTOGENERATED: DO NOT MODIFY"); - writer.AppendLine($"// edgeql:{hash}"); + writer.AppendLine($"// edgeql:{targetInfo.EdgeQLHash}"); writer.AppendLine($"// Generated on {DateTime.UtcNow:O}"); writer.AppendLine("#nullable enable"); writer.AppendLine($"using EdgeDB;"); @@ -85,9 +128,9 @@ private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, stri } // create the executor class - var classScope = writer.BeginScope($"public static class {resultName}"); + var classScope = writer.BeginScope($"public static class {targetInfo.FileNameWithoutExtension}"); - writer.AppendLine($"public static readonly string Query = @\"{edgeql}\";"); + writer.AppendLine($"public static readonly string Query = @\"{targetInfo.EdgeQL}\";"); writer.AppendLine(); var method = parseResult.Cardinality switch { @@ -126,7 +169,7 @@ private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, stri writer.AppendLine($" => client.{method}<{typeName ?? mainResult}>(Query{(methodArgs.Any() ? $", new Dictionary() {{ {string.Join(", ", methodArgs)} }}" : "")}, capabilities: (Capabilities){(ulong)parseResult.Capabilities}ul, token: token);"); writer.AppendLine(); - writer.AppendLine($"public static Task<{resultType}> {resultName}Async(this IEdgeDBQueryable client{(argParameters.Any() ? $", {string.Join(", ", argParameters)}" : "")}, CancellationToken token = default)"); + writer.AppendLine($"public static Task<{resultType}> {targetInfo.FileNameWithoutExtension}Async(this IEdgeDBQueryable client{(argParameters.Any() ? $", {string.Join(", ", argParameters)}" : "")}, CancellationToken token = default)"); writer.AppendLine($" => ExecuteAsync(client{(argParameters.Any() ? $", {string.Join(", ", argParameters.Select(x => x.Split(' ')[1]))}" : "")}, token: token);"); classScope.Dispose(); @@ -135,8 +178,8 @@ private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, stri return new() { - ExecuterClassName = resultName, - EdgeQLHash = hash, + ExecuterClassName = targetInfo.FileNameWithoutExtension, + EdgeQLHash = targetInfo.EdgeQLHash, ReturnResult = resultType, Parameters = argParameters, Code = writer.ToString() diff --git a/src/EdgeDB.Net.CLI/Properties/launchSettings.json b/src/EdgeDB.Net.CLI/Properties/launchSettings.json index 3ea913d2..436503f5 100644 --- a/src/EdgeDB.Net.CLI/Properties/launchSettings.json +++ b/src/EdgeDB.Net.CLI/Properties/launchSettings.json @@ -2,7 +2,8 @@ "profiles": { "EdgeDB.Net.CLI": { "commandName": "Project", - "commandLineArgs": "generate -f" + //"commandLineArgs": "file-watch-internal --dir C:\\Users\\lynch\\source\\repos\\EdgeDB" + "commandLineArgs": "generate -o C:\\Users\\lynch\\source\\repos\\EdgeDB\\examples\\EdgeDB.Examples.GenerationExample\\Generated" } } } \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs index 2711d3fd..970e66a9 100644 --- a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs @@ -28,8 +28,8 @@ public static string GetProjectRoot() public static async Task CreateGeneratedProjectAsync(string root, string name) { var result = await Cli.Wrap("dotnet") - .WithArguments($"new classlib --framework \"net6.0\" -o {name}") - .WithWorkingDirectory(root) + .WithArguments($"new classlib --framework \"net6.0\" -n {name}") + .WithWorkingDirectory(Directory.GetParent(root)?.FullName ?? root) .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) .ExecuteAsync(); @@ -39,7 +39,7 @@ public static async Task CreateGeneratedProjectAsync(string root, string name) result = await Cli.Wrap("dotnet") .WithArguments("add package EdgeDB.Net.Driver") - .WithWorkingDirectory(Path.Combine(root, name)) + .WithWorkingDirectory(Path.Combine(Directory.GetParent(root)?.FullName ?? root, name)) .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) .ExecuteAsync(); diff --git a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs index 7f60fac6..a0f87843 100644 --- a/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs +++ b/src/EdgeDB.Net.Driver/Clients/EdgeDBBinaryClient.cs @@ -693,7 +693,10 @@ private void ParseServerSettings(ParameterStatus status) /// public override async ValueTask ConnectAsync(CancellationToken token = default) { - await _connectSemaphone.WaitAsync(); + if (IsConnected) + return; + + await _connectSemaphone.WaitAsync(token); try { From 88153d1f3ca1734566b8f8adecbd518dc8367268 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Fri, 26 Aug 2022 11:05:16 -0300 Subject: [PATCH 05/13] CLI tool watch mode work --- .gitignore | 3 + EdgeDB.Net.sln | 9 ++- UpdateUser.g.cs | 32 ++++++++ .../EdgeDB.Examples.GenerationExample.csproj | 4 + .../EdgeDB.Generated/Class1.cs | 5 -- .../EdgeDB.Generated/CreateUser.g.cs | 34 ++++++++ .../EdgeDB.Generated/EdgeDB.Generated.csproj | 4 + .../GetUser.g.cs} | 12 +-- .../Program.cs | 13 +++- .../Scrips/CreateUser.edgeql | 6 ++ .../Scrips/UpdateUser.edgeql | 4 + src/EdgeDB.Net.CLI/Commands/FileWatch.cs | 78 +++++++++++++------ src/EdgeDB.Net.CLI/Commands/Generate.cs | 44 ++++++++++- src/EdgeDB.Net.CLI/EdgeQLParser.cs | 2 +- .../Properties/launchSettings.json | 4 +- src/EdgeDB.Net.CLI/Utils/FileUtils.cs | 32 ++++++++ src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs | 43 +++++++++- src/EdgeDB.Net.CLI/Utils/TextUtils.cs | 6 +- .../SchemaTypeBuilders/TypeBuilder.cs | 2 +- src/EdgeDB.Net.Driver/Utils/ConfigUtils.cs | 9 +-- 20 files changed, 294 insertions(+), 52 deletions(-) create mode 100644 UpdateUser.g.cs delete mode 100644 examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/Class1.cs create mode 100644 examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/CreateUser.g.cs rename examples/EdgeDB.Examples.GenerationExample/{Getuser.g.cs => EdgeDB.Generated/GetUser.g.cs} (73%) create mode 100644 examples/EdgeDB.Examples.GenerationExample/Scrips/CreateUser.edgeql create mode 100644 examples/EdgeDB.Examples.GenerationExample/Scrips/UpdateUser.edgeql create mode 100644 src/EdgeDB.Net.CLI/Utils/FileUtils.cs diff --git a/.gitignore b/.gitignore index 2c69ae7b..c7df4fee 100644 --- a/.gitignore +++ b/.gitignore @@ -375,3 +375,6 @@ MigrationBackup/ # Fody - auto-generated XML schema FodyWeavers.xsd src/EdgeDB.ExampleApp/dump.db + +# EdgeDB.Net CLI watcher info file +edgeql.dotnet.watcher.process \ No newline at end of file diff --git a/EdgeDB.Net.sln b/EdgeDB.Net.sln index e5d40fd9..459228e6 100644 --- a/EdgeDB.Net.sln +++ b/EdgeDB.Net.sln @@ -37,7 +37,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Net.CLI", "src\EdgeD EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "CLI Generation Example", "CLI Generation Example", "{46E94884-7A3D-4B48-9734-916ECCCB85C0}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EdgeDB.Examples.GenerationExample", "examples\EdgeDB.Examples.GenerationExample\EdgeDB.Examples.GenerationExample.csproj", "{4BCAA352-8488-46A9-B3C9-D2C50E937AFC}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Examples.GenerationExample", "examples\EdgeDB.Examples.GenerationExample\EdgeDB.Examples.GenerationExample.csproj", "{4BCAA352-8488-46A9-B3C9-D2C50E937AFC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "EdgeDB.Generated", "examples\EdgeDB.Examples.GenerationExample\EdgeDB.Generated\EdgeDB.Generated.csproj", "{E03D7BD1-B093-44FF-B276-410345430523}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -93,6 +95,10 @@ Global {4BCAA352-8488-46A9-B3C9-D2C50E937AFC}.Debug|Any CPU.Build.0 = Debug|Any CPU {4BCAA352-8488-46A9-B3C9-D2C50E937AFC}.Release|Any CPU.ActiveCfg = Release|Any CPU {4BCAA352-8488-46A9-B3C9-D2C50E937AFC}.Release|Any CPU.Build.0 = Release|Any CPU + {E03D7BD1-B093-44FF-B276-410345430523}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E03D7BD1-B093-44FF-B276-410345430523}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E03D7BD1-B093-44FF-B276-410345430523}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E03D7BD1-B093-44FF-B276-410345430523}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -111,6 +117,7 @@ Global {77D1980E-9835-4D16-B7B4-6CB5A4BD570C} = {025AAADF-16AF-4367-9C3D-9E60EDED832F} {46E94884-7A3D-4B48-9734-916ECCCB85C0} = {6FC214F5-C912-4D99-91B1-3E9F52A4E11B} {4BCAA352-8488-46A9-B3C9-D2C50E937AFC} = {46E94884-7A3D-4B48-9734-916ECCCB85C0} + {E03D7BD1-B093-44FF-B276-410345430523} = {46E94884-7A3D-4B48-9734-916ECCCB85C0} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {4E90C94F-D693-4411-82F3-2051DE1BE052} diff --git a/UpdateUser.g.cs b/UpdateUser.g.cs new file mode 100644 index 00000000..a753e70d --- /dev/null +++ b/UpdateUser.g.cs @@ -0,0 +1,32 @@ +// AUTOGENERATED: DO NOT MODIFY +// edgeql:8C0C1D118ED8DCA142C4EE3EA651624B7724DCE93E948675ACD9CBF5A332FDBB +// Generated on 2022-08-26T13:31:14.0776438Z +#nullable enable +using EdgeDB; + +namespace EdgeDB.Generated; + +#region Types +[EdgeDBType] +public sealed class UpdateUserResult +{ + [EdgeDBProperty("id")] + public Guid Id { get; set; } +} + +#endregion + +public static class UpdateUser +{ + public static readonly string Query = @"UPDATE Person FILTER .id = $id SET { + name := $name, + email := $email, +}"; + + public static Task> ExecuteAsync(IEdgeDBQueryable client, Guid id, String? name, String? email, CancellationToken token = default) + => client.QueryAsync(Query, new Dictionary() { { "id", id }, { "name", name }, { "email", email } }, capabilities: (Capabilities)18446744073709551615ul, token: token); + + public static Task> UpdateUserAsync(this IEdgeDBQueryable client, Guid id, String? name, String? email, CancellationToken token = default) + => ExecuteAsync(client, id, name, email, token: token); +} +#nullable restore diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Examples.GenerationExample.csproj b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Examples.GenerationExample.csproj index c95ba71e..7ca5e342 100644 --- a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Examples.GenerationExample.csproj +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Examples.GenerationExample.csproj @@ -13,4 +13,8 @@ + + + + diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/Class1.cs b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/Class1.cs deleted file mode 100644 index 1dbe1e63..00000000 --- a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/Class1.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace EdgeDB.Generated; -public class Class1 -{ - -} diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/CreateUser.g.cs b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/CreateUser.g.cs new file mode 100644 index 00000000..0cf9674d --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/CreateUser.g.cs @@ -0,0 +1,34 @@ +// AUTOGENERATED: DO NOT MODIFY +// edgeql:B9692A5CA0A9992246361197BEACBDE398A5A30C5DCCC83BCACD8C80D5842FEB +// Generated on 2022-08-26T11:55:03.5378711Z +#nullable enable +using EdgeDB; + +namespace EdgeDB.Generated; + +#region Types +[EdgeDBType] +public sealed class CreateUserResult +{ + [EdgeDBProperty("id")] + public Guid Id { get; set; } +} + +#endregion + +public static class CreateUser +{ + public static readonly string Query = @"INSERT Person { + name := $name, + email := $email +} +UNLESS CONFLICT ON .email +ELSE (SELECT Person)"; + + public static Task ExecuteAsync(IEdgeDBQueryable client, String? name, String? email, CancellationToken token = default) + => client.QuerySingleAsync(Query, new Dictionary() { { "name", name }, { "email", email } }, capabilities: (Capabilities)1ul, token: token); + + public static Task CreateUserAsync(this IEdgeDBQueryable client, String? name, String? email, CancellationToken token = default) + => ExecuteAsync(client, name, email, token: token); +} +#nullable restore diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/EdgeDB.Generated.csproj b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/EdgeDB.Generated.csproj index 132c02c5..f4a98cb4 100644 --- a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/EdgeDB.Generated.csproj +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/EdgeDB.Generated.csproj @@ -6,4 +6,8 @@ enable + + + + diff --git a/examples/EdgeDB.Examples.GenerationExample/Getuser.g.cs b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/GetUser.g.cs similarity index 73% rename from examples/EdgeDB.Examples.GenerationExample/Getuser.g.cs rename to examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/GetUser.g.cs index ff395861..7ba9436a 100644 --- a/examples/EdgeDB.Examples.GenerationExample/Getuser.g.cs +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/GetUser.g.cs @@ -1,6 +1,6 @@ // AUTOGENERATED: DO NOT MODIFY // edgeql:E116C5881529C70BFC6A9D0350075968AD967D391FEE0F74033FA870A1FE56DD -// Generated on 2022-08-24T13:50:21.2137406Z +// Generated on 2022-08-26T11:52:03.5962767Z #nullable enable using EdgeDB; @@ -8,7 +8,7 @@ namespace EdgeDB.Generated; #region Types [EdgeDBType] -public sealed class GetuserResult +public sealed class GetUserResult { [EdgeDBProperty("id")] public Guid Id { get; set; } @@ -22,17 +22,17 @@ public sealed class GetuserResult #endregion -public static class Getuser +public static class GetUser { public static readonly string Query = @"select Person { name, email } filter .email = $email"; - public static Task ExecuteAsync(IEdgeDBQueryable client, String? email, CancellationToken token = default) - => client.QuerySingleAsync(Query, new Dictionary() { { "email", email } }, capabilities: (Capabilities)0ul, token: token); + public static Task ExecuteAsync(IEdgeDBQueryable client, String? email, CancellationToken token = default) + => client.QuerySingleAsync(Query, new Dictionary() { { "email", email } }, capabilities: (Capabilities)0ul, token: token); - public static Task GetuserAsync(this IEdgeDBQueryable client, String? email, CancellationToken token = default) + public static Task GetUserAsync(this IEdgeDBQueryable client, String? email, CancellationToken token = default) => ExecuteAsync(client, email, token: token); } #nullable restore diff --git a/examples/EdgeDB.Examples.GenerationExample/Program.cs b/examples/EdgeDB.Examples.GenerationExample/Program.cs index 3751555c..6a530b2a 100644 --- a/examples/EdgeDB.Examples.GenerationExample/Program.cs +++ b/examples/EdgeDB.Examples.GenerationExample/Program.cs @@ -1,2 +1,11 @@ -// See https://aka.ms/new-console-template for more information -Console.WriteLine("Hello, World!"); +using EdgeDB; +using EdgeDB.Generated; + +// create a client +var client = new EdgeDBClient(); + +// create a user +await client.CreateUserAsync(name: "example", email: "example@example.com"); + +// Get a user based on email +var user = await client.GetUserAsync(email: "example@example.com"); diff --git a/examples/EdgeDB.Examples.GenerationExample/Scrips/CreateUser.edgeql b/examples/EdgeDB.Examples.GenerationExample/Scrips/CreateUser.edgeql new file mode 100644 index 00000000..e65e9a37 --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/Scrips/CreateUser.edgeql @@ -0,0 +1,6 @@ +INSERT Person { + name := $name, + email := $email +} +UNLESS CONFLICT ON .email +ELSE (SELECT Person) \ No newline at end of file diff --git a/examples/EdgeDB.Examples.GenerationExample/Scrips/UpdateUser.edgeql b/examples/EdgeDB.Examples.GenerationExample/Scrips/UpdateUser.edgeql new file mode 100644 index 00000000..6c442ff0 --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/Scrips/UpdateUser.edgeql @@ -0,0 +1,4 @@ +UPDATE Person FILTER .id = $id SET { + name := $name, + email := $email, +} \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs index 7ee53009..370ee47b 100644 --- a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs +++ b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs @@ -1,5 +1,7 @@ using CommandLine; using EdgeDB.CLI; +using EdgeDB.CLI.Utils; +using EdgeDB.Net.CLI.Utils; using Newtonsoft.Json; using System; using System.Collections.Generic; @@ -22,24 +24,24 @@ internal class FileWatch : ICommand public string? Namespace { get; set; } private readonly FileSystemWatcher _watcher = new(); - private readonly EdgeDBTcpClient _client; - public FileWatch() + private EdgeDBTcpClient? _client; + private readonly SemaphoreSlim _mutex = new(1, 1); + + + public async Task ExecuteAsync() { if (Connection is null) throw new InvalidOperationException("Connection must be specified"); _client = new(JsonConvert.DeserializeObject(Connection)!, new()); - } - public async Task ExecuteAsync() - { - _watcher.Path = Dir; + _watcher.Path = Dir!; _watcher.Filter = "*.edgeql"; _watcher.IncludeSubdirectories = true; _watcher.Error += _watcher_Error; - _watcher.Changed += _watcher_Changed; - _watcher.Created += _watcher_Created; + _watcher.Changed += CreatedAndUpdated; + _watcher.Created += CreatedAndUpdated; _watcher.Deleted += _watcher_Deleted; _watcher.Renamed += _watcher_Renamed; @@ -54,41 +56,73 @@ public async Task ExecuteAsync() _watcher.EnableRaisingEvents = true; + ProjectUtils.RegisterProcessAsWatcher(Dir!); + await Task.Delay(-1); } public async Task GenerateAsync(EdgeQLParser.GenerationTargetInfo info) { - await _client.ConnectAsync(); - - var result = await EdgeQLParser.ParseAndGenerateAsync(_client, Namespace!, info); - - File.WriteAllText(info.TargetFilePath!, result.Code); + await _mutex.WaitAsync().ConfigureAwait(false); + + try + { + await _client!.ConnectAsync(); + + try + { + var result = await EdgeQLParser.ParseAndGenerateAsync(_client, Namespace!, info); + File.WriteAllText(info.TargetFilePath!, result.Code); + } + catch (EdgeDBErrorException err) + { + // error with file + Console.WriteLine(err.Message); + } + } + catch(Exception x) + { + Console.WriteLine(x); + } + finally + { + _mutex.Release(); + } } - private void _watcher_Created(object sender, FileSystemEventArgs e) + private void CreatedAndUpdated(object sender, FileSystemEventArgs e) { + FileUtils.WaitForHotFile(e.FullPath); + var info = EdgeQLParser.GetTargetInfo(e.FullPath, Dir!); if (info.GeneratedTargetExistsAndIsUpToDate()) return; - Task.Run(async () => await GenerateAsync(info)); - } - - private void _watcher_Changed(object sender, FileSystemEventArgs e) - { - throw new NotImplementedException(); + Task.Run(async () => + { + await Task.Delay(200); + await GenerateAsync(info); + }); } private void _watcher_Deleted(object sender, FileSystemEventArgs e) { - throw new NotImplementedException(); + var info = EdgeQLParser.GetTargetInfo(e.FullPath, Dir!); + + if (File.Exists(info.TargetFilePath)) + File.Delete(info.TargetFilePath); } private void _watcher_Renamed(object sender, RenamedEventArgs e) { - throw new NotImplementedException(); + var info = EdgeQLParser.GetTargetInfo(e.OldFullPath, Dir!); + + if (File.Exists(info.TargetFilePath)) + { + var newInfo = EdgeQLParser.GetTargetInfo(e.FullPath, Dir!); + File.Move(info.TargetFilePath, newInfo.TargetFilePath!); + } } private void _watcher_Error(object sender, ErrorEventArgs e) diff --git a/src/EdgeDB.Net.CLI/Commands/Generate.cs b/src/EdgeDB.Net.CLI/Commands/Generate.cs index 660c5a20..79858802 100644 --- a/src/EdgeDB.Net.CLI/Commands/Generate.cs +++ b/src/EdgeDB.Net.CLI/Commands/Generate.cs @@ -2,6 +2,8 @@ using EdgeDB.CLI.Arguments; using EdgeDB.CLI.Utils; using EdgeDB.Codecs; +using Newtonsoft.Json; +using System.Diagnostics; using System.Reflection; using System.Text.RegularExpressions; @@ -21,7 +23,10 @@ public class Generate : ConnectionArguments, ICommand [Option('f', "force", HelpText = "Force regeneration of files")] public bool Force { get; set; } - + + [Option("watch", HelpText = "Listens for any changes or new edgeql files and generates them automatically")] + public bool Watch { get; set; } + public async Task ExecuteAsync() { // get connection info @@ -43,8 +48,10 @@ public async Task ExecuteAsync() { Console.WriteLine($"Creating project {GeneratedProjectName}..."); await ProjectUtils.CreateGeneratedProjectAsync(OutputDirectory, GeneratedProjectName); - OutputDirectory = Path.Combine(OutputDirectory, GeneratedProjectName); } + + if(GenerateProject) + OutputDirectory = Path.Combine(OutputDirectory, GeneratedProjectName); // find edgeql files var edgeqlFiles = ProjectUtils.GetTargetEdgeQLFiles(projectRoot).ToArray(); @@ -80,5 +87,38 @@ public async Task ExecuteAsync() } Console.WriteLine("Generation complete!"); + + if(Watch) + { + var existing = ProjectUtils.GetWatcherProcess(projectRoot); + + if(existing is not null) + { + Console.WriteLine("Watching already running"); + return; + } + + StartWatchProcess(connection); + } + } + + public void StartWatchProcess(EdgeDBConnection connection) + { + var current = Process.GetCurrentProcess(); + var connString = JsonConvert.SerializeObject(connection).Replace("\"", "\\\""); + + Process.Start(new ProcessStartInfo + { + FileName = current.MainModule.FileName, + Arguments = $"file-watch-internal --connection \"{connString}\" --dir \"{OutputDirectory}\" --namespace \"{GeneratedProjectName}\"", + UseShellExecute = true, + }); + + + + + //process.StartInfo.Arguments = + //process.StartInfo.UseShellExecute = true; + //process.Start(); } } \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/EdgeQLParser.cs b/src/EdgeDB.Net.CLI/EdgeQLParser.cs index d14fd1ae..b44e3352 100644 --- a/src/EdgeDB.Net.CLI/EdgeQLParser.cs +++ b/src/EdgeDB.Net.CLI/EdgeQLParser.cs @@ -31,7 +31,7 @@ public static bool TargetFileHashMatches(string header, string hash) public static GenerationTargetInfo GetTargetInfo(string edgeqlFile, string targetDir) { - var fileContent = File.ReadAllText(edgeqlFile); + string fileContent = File.ReadAllText(edgeqlFile); var hash = HashUtils.HashEdgeQL(fileContent); var fileName = TextUtils.ToPascalCase(Path.GetFileName(edgeqlFile).Split('.')[0]); diff --git a/src/EdgeDB.Net.CLI/Properties/launchSettings.json b/src/EdgeDB.Net.CLI/Properties/launchSettings.json index 436503f5..ee546b5a 100644 --- a/src/EdgeDB.Net.CLI/Properties/launchSettings.json +++ b/src/EdgeDB.Net.CLI/Properties/launchSettings.json @@ -2,8 +2,8 @@ "profiles": { "EdgeDB.Net.CLI": { "commandName": "Project", - //"commandLineArgs": "file-watch-internal --dir C:\\Users\\lynch\\source\\repos\\EdgeDB" - "commandLineArgs": "generate -o C:\\Users\\lynch\\source\\repos\\EdgeDB\\examples\\EdgeDB.Examples.GenerationExample\\Generated" + "commandLineArgs": "file-watch-internal --dir C:\\Users\\lynch\\source\\repos\\EdgeDB --namespace EdgeDB.Generated --connection \"{\\\"user\\\":\\\"edgedb\\\",\\\"password\\\":\\\"x3WnskHEbmXBvnT1Vf14ABuL\\\",\\\"Hostname\\\":\\\"127.0.0.1\\\",\\\"port\\\":10711,\\\"database\\\":\\\"edgedb\\\",\\\"tls_cert_data\\\":\\\"-----BEGIN CERTIFICATE-----\nMIIC0zCCAbugAwIBAgIRAPQegLaiakFsl0hPzNUeEaAwDQYJKoZIhvcNAQELBQAw\nGDEWMBQGA1UEAwwNRWRnZURCIFNlcnZlcjAeFw0yMjA3MjIxMzA1MzBaFw00MTA5\nMjExMzA1MzBaMBgxFjAUBgNVBAMMDUVkZ2VEQiBTZXJ2ZXIwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDOSRrWTy9oC9J7CWvGKg9Rg7o/R1D4AQO++rML\npSBd1obrSlJB/p5kLWsMuSM29dgRubqqY7PrrjvcDedpDwyAp9JeIz6tIkkqo+/d\n8dRCaV/3wHa5BgfN8fEE6blnmdrQ3asuVafZ989+qoa1PjLc9iU5JJgp/gb/SS7Z\n02L+0y+vw/QjvQcRoiDfCcIfasp6Z2Jor9RCd+AchNG7m0teQ2iK9BXG33QJUXdA\nYwXEALQxJCi23v197xZQUJbXlxh4+YIFJiYA95gXzoB/lBUo/CQSNwxzkofwLtV/\nWT+CmAP25D/v6/rLWi+ps+vM5GFcIetMK0Bk8yzkr+EXB07/AgMBAAGjGDAWMBQG\nA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAUDNnRYoHCtW5\nRMBBf0YTr9zpLA69cK9J6OfY7wPW1ROd3/qmPz57fJ1DOq+5R4U8+rAvk6pO5p6l\nLG3RyQrC5bJuhyxxcBMkc43INC641gMrJlMm4SSRI0HO1nYiZLFdyUjTfrH//ThA\nFVDwAFR/Xw97R1qZQdZYLhxdnRjYIySWUs8yBljtuEB5iorDX9AaXQ25kZ4f8eTE\nsmxezVKQks+FUPdX6yhb++uWvqqzK2flWuiztZmQWLCQnydKuwyBx4izhsiSZ3wL\nbdO8MaTbq2o2dzvzQAWxsf8Wg1Xogep4PA0goquTJvgjzcHuEtyl8404Mb4U/Fpy\ngIFB7zA3bA==\n-----END CERTIFICATE-----\n\\\",\\\"tls_ca\\\":\\\"-----BEGIN CERTIFICATE-----\nMIIC0zCCAbugAwIBAgIRAPQegLaiakFsl0hPzNUeEaAwDQYJKoZIhvcNAQELBQAw\nGDEWMBQGA1UEAwwNRWRnZURCIFNlcnZlcjAeFw0yMjA3MjIxMzA1MzBaFw00MTA5\nMjExMzA1MzBaMBgxFjAUBgNVBAMMDUVkZ2VEQiBTZXJ2ZXIwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDOSRrWTy9oC9J7CWvGKg9Rg7o/R1D4AQO++rML\npSBd1obrSlJB/p5kLWsMuSM29dgRubqqY7PrrjvcDedpDwyAp9JeIz6tIkkqo+/d\n8dRCaV/3wHa5BgfN8fEE6blnmdrQ3asuVafZ989+qoa1PjLc9iU5JJgp/gb/SS7Z\n02L+0y+vw/QjvQcRoiDfCcIfasp6Z2Jor9RCd+AchNG7m0teQ2iK9BXG33QJUXdA\nYwXEALQxJCi23v197xZQUJbXlxh4+YIFJiYA95gXzoB/lBUo/CQSNwxzkofwLtV/\nWT+CmAP25D/v6/rLWi+ps+vM5GFcIetMK0Bk8yzkr+EXB07/AgMBAAGjGDAWMBQG\nA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAUDNnRYoHCtW5\nRMBBf0YTr9zpLA69cK9J6OfY7wPW1ROd3/qmPz57fJ1DOq+5R4U8+rAvk6pO5p6l\nLG3RyQrC5bJuhyxxcBMkc43INC641gMrJlMm4SSRI0HO1nYiZLFdyUjTfrH//ThA\nFVDwAFR/Xw97R1qZQdZYLhxdnRjYIySWUs8yBljtuEB5iorDX9AaXQ25kZ4f8eTE\nsmxezVKQks+FUPdX6yhb++uWvqqzK2flWuiztZmQWLCQnydKuwyBx4izhsiSZ3wL\nbdO8MaTbq2o2dzvzQAWxsf8Wg1Xogep4PA0goquTJvgjzcHuEtyl8404Mb4U/Fpy\ngIFB7zA3bA==\n-----END CERTIFICATE-----\n\\\",\\\"tls_security\\\":0}\"" + //"commandLineArgs": "generate -o C:\\Users\\lynch\\source\\repos\\EdgeDB\\examples\\EdgeDB.Examples.GenerationExample --watch" } } } \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Utils/FileUtils.cs b/src/EdgeDB.Net.CLI/Utils/FileUtils.cs new file mode 100644 index 00000000..8634d194 --- /dev/null +++ b/src/EdgeDB.Net.CLI/Utils/FileUtils.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.Net.CLI.Utils +{ + internal class FileUtils + { + public static bool WaitForHotFile(string path, int timeout = 5000) + { + var start = DateTime.UtcNow; + while (true) + { + try + { + using var fs = File.Open(path, FileMode.Open, FileAccess.ReadWrite, FileShare.None); + fs.Close(); + return true; + } + catch + { + if ((DateTime.UtcNow - start).TotalMilliseconds >= timeout) + return false; + + Thread.Sleep(200); + } + } + } + } +} diff --git a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs index 970e66a9..3f606e61 100644 --- a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs @@ -1,12 +1,13 @@ using CliWrap; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks; namespace EdgeDB.CLI.Utils -{ +{ internal class ProjectUtils { public static string GetProjectRoot() @@ -25,11 +26,47 @@ public static string GetProjectRoot() return directory; } + public static Process? GetWatcherProcess(string root) + { + var file = Path.Combine(root, "edgeql.dotnet.watcher.process"); + if (File.Exists(file) && int.TryParse(File.ReadAllText(file), out var id)) + { + try + { + return Process.GetProcessById(id); + } + catch { return null; } + } + + return null; + } + + public static void RegisterProcessAsWatcher(string root) + { + var id = Process.GetCurrentProcess().Id; + + File.WriteAllText(Path.Combine(root, "edgeql.dotnet.watcher.process"), $"{id}"); + + // add to gitignore if its here + var gitignore = Path.Combine(root, ".gitignore"); + if (File.Exists(gitignore)) + { + var contents = File.ReadAllText(gitignore); + + if(!contents.Contains("edgeql.dotnet.watcher.process")) + { + contents += $"{Environment.NewLine}# EdgeDB.Net CLI watcher info file{Environment.NewLine}edgeql.dotnet.watcher.process"; + File.WriteAllText(gitignore, contents); + } + } + + } + public static async Task CreateGeneratedProjectAsync(string root, string name) { var result = await Cli.Wrap("dotnet") .WithArguments($"new classlib --framework \"net6.0\" -n {name}") - .WithWorkingDirectory(Directory.GetParent(root)?.FullName ?? root) + .WithWorkingDirectory(root) .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) .ExecuteAsync(); @@ -39,7 +76,7 @@ public static async Task CreateGeneratedProjectAsync(string root, string name) result = await Cli.Wrap("dotnet") .WithArguments("add package EdgeDB.Net.Driver") - .WithWorkingDirectory(Path.Combine(Directory.GetParent(root)?.FullName ?? root, name)) + .WithWorkingDirectory(Path.Combine(root, name)) .WithStandardErrorPipe(PipeTarget.ToStream(Console.OpenStandardError())) .WithStandardOutputPipe(PipeTarget.ToStream(Console.OpenStandardOutput())) .ExecuteAsync(); diff --git a/src/EdgeDB.Net.CLI/Utils/TextUtils.cs b/src/EdgeDB.Net.CLI/Utils/TextUtils.cs index 7b6cb9c1..c5feec6b 100644 --- a/src/EdgeDB.Net.CLI/Utils/TextUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/TextUtils.cs @@ -3,6 +3,7 @@ using System.Globalization; using System.Linq; using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; namespace EdgeDB.CLI.Utils @@ -14,8 +15,9 @@ internal class TextUtils public static string ToPascalCase(string input) { _cultureInfo ??= CultureInfo.CurrentCulture; - - return _cultureInfo.TextInfo.ToTitleCase(input.Replace("_", " ")).Replace(" ", ""); + var t = Regex.Replace(input, @"[^^]([A-Z])", m => $"{m.Value[0]} {m.Groups[1].Value}"); + + return _cultureInfo.TextInfo.ToTitleCase(t.Replace("_", " ")).Replace(" ", ""); } public static string ToCamelCase(string input) diff --git a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs index f17345a6..7d9c6410 100644 --- a/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs +++ b/src/EdgeDB.Net.Driver/Serializer/SchemaTypeBuilders/TypeBuilder.cs @@ -127,7 +127,7 @@ internal static bool IsValidObjectType(Type type) ?.GetCustomAttribute() != null; // allow abstract passthru - return type.IsAbstract ? true : (type.IsClass || type.IsValueType) && !type.IsSealed && validConstructor; + return type.IsAbstract ? true : (type.IsClass || type.IsValueType) && validConstructor; } internal static bool TryGetCollectionParser(Type type, out Func? builder) diff --git a/src/EdgeDB.Net.Driver/Utils/ConfigUtils.cs b/src/EdgeDB.Net.Driver/Utils/ConfigUtils.cs index c0074813..c5fe0d75 100644 --- a/src/EdgeDB.Net.Driver/Utils/ConfigUtils.cs +++ b/src/EdgeDB.Net.Driver/Utils/ConfigUtils.cs @@ -7,14 +7,13 @@ namespace EdgeDB.Utils internal class ConfigUtils { public static string EdgeDBConfigDir - => Path.Combine(GetEdgeDBBasePath(), "config"); + => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? Path.Combine(GetEdgeDBBasePath(), "config") + : GetEdgeDBBasePath(); public static string CredentialsDir => Path.Combine(EdgeDBConfigDir, "credentials"); - public static string ProjectsDir - => Path.Combine(EdgeDBConfigDir, "projects"); - private static string GetEdgeDBKnownBasePath() { if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) @@ -51,7 +50,7 @@ public static string GetInstanceProjectDirectory(string projectDir) using (var sha1 = SHA1.Create()) hash = HexConverter.ToHex(sha1.ComputeHash(Encoding.UTF8.GetBytes(fullPath))); - return Path.Combine(GetEdgeDBBasePath(), "config", "projects", $"{baseName}-{hash.ToLower()}"); + return Path.Combine(EdgeDBConfigDir, "projects", $"{baseName}-{hash.ToLower()}"); } } } From 822cedbe7b5cf2d27eed7f807371eff9cacd06b7 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 28 Aug 2022 09:52:53 -0300 Subject: [PATCH 06/13] watch mode --- UpdateUser.g.cs | 32 -- .../EdgeDB.Generated/DeleteUser.g.cs | 29 ++ .../EdgeDB.Generated/UpdateUser.g.cs | 29 ++ .../Scrips/DeleteUser.edgeql | 1 + .../Scrips/UpdateUser.edgeql | 5 +- .../Arguments/ConnectionArguments.cs | 2 +- src/EdgeDB.Net.CLI/Arguments/LogArgs.cs | 17 + src/EdgeDB.Net.CLI/Commands/FileWatch.cs | 142 +++++- src/EdgeDB.Net.CLI/Commands/Generate.cs | 52 +- src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj | 2 + src/EdgeDB.Net.CLI/ICommand.cs | 6 +- src/EdgeDB.Net.CLI/Program.cs | 20 +- .../Properties/launchSettings.json | 4 +- src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs | 455 ------------------ src/EdgeDB.Net.CLI/Utils/FileUtils.cs | 2 +- src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs | 16 +- .../Utils/QueryErrorFormatter.cs | 37 -- 17 files changed, 260 insertions(+), 591 deletions(-) delete mode 100644 UpdateUser.g.cs create mode 100644 examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/DeleteUser.g.cs create mode 100644 examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/UpdateUser.g.cs create mode 100644 examples/EdgeDB.Examples.GenerationExample/Scrips/DeleteUser.edgeql create mode 100644 src/EdgeDB.Net.CLI/Arguments/LogArgs.cs delete mode 100644 src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs delete mode 100644 src/EdgeDB.Net.CLI/Utils/QueryErrorFormatter.cs diff --git a/UpdateUser.g.cs b/UpdateUser.g.cs deleted file mode 100644 index a753e70d..00000000 --- a/UpdateUser.g.cs +++ /dev/null @@ -1,32 +0,0 @@ -// AUTOGENERATED: DO NOT MODIFY -// edgeql:8C0C1D118ED8DCA142C4EE3EA651624B7724DCE93E948675ACD9CBF5A332FDBB -// Generated on 2022-08-26T13:31:14.0776438Z -#nullable enable -using EdgeDB; - -namespace EdgeDB.Generated; - -#region Types -[EdgeDBType] -public sealed class UpdateUserResult -{ - [EdgeDBProperty("id")] - public Guid Id { get; set; } -} - -#endregion - -public static class UpdateUser -{ - public static readonly string Query = @"UPDATE Person FILTER .id = $id SET { - name := $name, - email := $email, -}"; - - public static Task> ExecuteAsync(IEdgeDBQueryable client, Guid id, String? name, String? email, CancellationToken token = default) - => client.QueryAsync(Query, new Dictionary() { { "id", id }, { "name", name }, { "email", email } }, capabilities: (Capabilities)18446744073709551615ul, token: token); - - public static Task> UpdateUserAsync(this IEdgeDBQueryable client, Guid id, String? name, String? email, CancellationToken token = default) - => ExecuteAsync(client, id, name, email, token: token); -} -#nullable restore diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/DeleteUser.g.cs b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/DeleteUser.g.cs new file mode 100644 index 00000000..07e05e9c --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/DeleteUser.g.cs @@ -0,0 +1,29 @@ +// AUTOGENERATED: DO NOT MODIFY +// edgeql:28E22B161B70DB5DE661AC86709BEADD3F9DC475559DE90F194CBAF24A5A880C +// Generated on 2022-08-28T12:51:13.6300794Z +#nullable enable +using EdgeDB; + +namespace EdgeDB.Generated; + +#region Types +[EdgeDBType] +public sealed class DeleteUserResult +{ + [EdgeDBProperty("id")] + public Guid Id { get; set; } +} + +#endregion + +public static class DeleteUser +{ + public static readonly string Query = @"delete Person filter .email = $email"; + + public static Task ExecuteAsync(IEdgeDBQueryable client, String? email, CancellationToken token = default) + => client.QuerySingleAsync(Query, new Dictionary() { { "email", email } }, capabilities: (Capabilities)1ul, token: token); + + public static Task DeleteUserAsync(this IEdgeDBQueryable client, String? email, CancellationToken token = default) + => ExecuteAsync(client, email, token: token); +} +#nullable restore diff --git a/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/UpdateUser.g.cs b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/UpdateUser.g.cs new file mode 100644 index 00000000..8023db81 --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/EdgeDB.Generated/UpdateUser.g.cs @@ -0,0 +1,29 @@ +// AUTOGENERATED: DO NOT MODIFY +// edgeql:A1C5568E24B561F8D8CE4109C9E8E604C801BF104AFA3A4C07A0F2EDB720AC27 +// Generated on 2022-08-26T15:33:42.9431602Z +#nullable enable +using EdgeDB; + +namespace EdgeDB.Generated; + +#region Types +[EdgeDBType] +public sealed class UpdateUserResult +{ + [EdgeDBProperty("id")] + public Guid Id { get; set; } +} + +#endregion + +public static class UpdateUser +{ + public static readonly string Query = @"update Person filter .id = $id set { name := $name, email := $email }"; + + public static Task ExecuteAsync(IEdgeDBQueryable client, Guid id, String? name, String? email, CancellationToken token = default) + => client.QuerySingleAsync(Query, new Dictionary() { { "id", id }, { "name", name }, { "email", email } }, capabilities: (Capabilities)1ul, token: token); + + public static Task UpdateUserAsync(this IEdgeDBQueryable client, Guid id, String? name, String? email, CancellationToken token = default) + => ExecuteAsync(client, id, name, email, token: token); +} +#nullable restore diff --git a/examples/EdgeDB.Examples.GenerationExample/Scrips/DeleteUser.edgeql b/examples/EdgeDB.Examples.GenerationExample/Scrips/DeleteUser.edgeql new file mode 100644 index 00000000..5a7b4883 --- /dev/null +++ b/examples/EdgeDB.Examples.GenerationExample/Scrips/DeleteUser.edgeql @@ -0,0 +1 @@ +delete Person filter .email = $email \ No newline at end of file diff --git a/examples/EdgeDB.Examples.GenerationExample/Scrips/UpdateUser.edgeql b/examples/EdgeDB.Examples.GenerationExample/Scrips/UpdateUser.edgeql index 6c442ff0..ded619fb 100644 --- a/examples/EdgeDB.Examples.GenerationExample/Scrips/UpdateUser.edgeql +++ b/examples/EdgeDB.Examples.GenerationExample/Scrips/UpdateUser.edgeql @@ -1,4 +1 @@ -UPDATE Person FILTER .id = $id SET { - name := $name, - email := $email, -} \ No newline at end of file +update Person filter .id = $id set { name := $name, email := $email } \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Arguments/ConnectionArguments.cs b/src/EdgeDB.Net.CLI/Arguments/ConnectionArguments.cs index 890af00f..7bde6ef0 100644 --- a/src/EdgeDB.Net.CLI/Arguments/ConnectionArguments.cs +++ b/src/EdgeDB.Net.CLI/Arguments/ConnectionArguments.cs @@ -9,7 +9,7 @@ namespace EdgeDB.CLI.Arguments { - public class ConnectionArguments + public class ConnectionArguments : LogArgs { [Option("dsn", HelpText = "DSN for EdgeDB to connect to (overrides all other options except password)")] public string? DSN { get; set; } diff --git a/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs b/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs new file mode 100644 index 00000000..243dbe34 --- /dev/null +++ b/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs @@ -0,0 +1,17 @@ +using CommandLine; +using Microsoft.Extensions.Logging; +using Serilog.Events; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace EdgeDB.CLI.Arguments +{ + public class LogArgs + { + [Option("loglevel")] + public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; + } +} diff --git a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs index 370ee47b..578fe6c8 100644 --- a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs +++ b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs @@ -1,18 +1,99 @@ using CommandLine; using EdgeDB.CLI; +using EdgeDB.CLI.Arguments; using EdgeDB.CLI.Utils; -using EdgeDB.Net.CLI.Utils; using Newtonsoft.Json; +using Serilog; using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace EdgeDB.Net.CLI.Commands +namespace EdgeDB.CLI.Commands { + [Verb("watch", HelpText = "Configure the file watcher")] + public class FileWatch : ConnectionArguments, ICommand + { + [Option('k', "kill", SetName = "functions", HelpText = "Kill the current running watcher for the project")] + public bool Kill { get; set; } + + [Option('s', "start", SetName = "functions", HelpText = "Start a watcher for the current project")] + public bool Start { get; set; } + + [Option('o', "output", HelpText = "The output directory for the generated source to be placed.")] + public string? OutputDirectory { get; set; } + + [Option('n', "project-name", HelpText = "The name of the generated project and namespace of generated files.")] + public string GeneratedProjectName { get; set; } = "EdgeDB.Generated"; + + public Task ExecuteAsync(ILogger logger) + { + // get project root + var root = ProjectUtils.GetProjectRoot(); + var watcher = ProjectUtils.GetWatcherProcess(root); + logger.Debug("Watcher?: {@Watcher} Project root: {@Root}", watcher is not null, root); + try + { + if (!Kill && !Start) + { + // display information about the current watcher + if (watcher is null) + { + logger.Information("No file watcher is running for {@Dir}", root); + return Task.CompletedTask; + } + + logger.Information("File watcher is watching {@Dir}", Path.Combine(root, "*.edgeql")); + logger.Information("Process ID: {@Id}", watcher.Id); + + return Task.CompletedTask; + } + + if (Kill) + { + if (watcher is null) + { + logger.Error("No watcher is running for {@Dir}", root); + return Task.CompletedTask; + } + + watcher.Kill(); + logger.Information("Watcher process {@PID} kiled", watcher.Id); + + return Task.CompletedTask; + } + + if (Start) + { + if (watcher is not null) + { + logger.Error("Watcher already running! Process ID: {@PID}", watcher.Id); + return Task.CompletedTask; + } + + var connection = GetConnection(); + + OutputDirectory ??= Path.Combine(root, GeneratedProjectName); + + var pid = ProjectUtils.StartWatchProcess(connection, root, OutputDirectory, GeneratedProjectName); + + logger.Information("Watcher process started, PID: {@PID}", pid); + } + + return Task.CompletedTask; + } + catch(Exception x) + { + logger.Error(x, "Failed to run watcher command"); + return Task.CompletedTask; + } + } + } + [Verb("file-watch-internal", Hidden = true)] - internal class FileWatch : ICommand + internal class FileWatchInternal : ICommand { [Option("connection")] public string? Connection { get; set; } @@ -20,15 +101,19 @@ internal class FileWatch : ICommand [Option("dir")] public string? Dir { get; set; } + [Option("output")] + public string? Output { get; set; } + [Option("namespace")] public string? Namespace { get; set; } private readonly FileSystemWatcher _watcher = new(); private EdgeDBTcpClient? _client; private readonly SemaphoreSlim _mutex = new(1, 1); + private readonly ConcurrentStack _writeQueue = new(); + private TaskCompletionSource _writeDispatcher = new(); - - public async Task ExecuteAsync() + public async Task ExecuteAsync(ILogger logger) { if (Connection is null) throw new InvalidOperationException("Connection must be specified"); @@ -40,6 +125,7 @@ public async Task ExecuteAsync() _watcher.IncludeSubdirectories = true; _watcher.Error += _watcher_Error; + _watcher.Changed += CreatedAndUpdated; _watcher.Created += CreatedAndUpdated; _watcher.Deleted += _watcher_Deleted; @@ -58,7 +144,19 @@ public async Task ExecuteAsync() ProjectUtils.RegisterProcessAsWatcher(Dir!); - await Task.Delay(-1); + logger.Information("Watcher started for {@Dir}", Output); + + while (true) + { + await _writeDispatcher.Task; + + while(_writeQueue.TryPop(out var info)) + { + await GenerateAsync(info); + } + + _writeDispatcher = new(); + } } public async Task GenerateAsync(EdgeQLParser.GenerationTargetInfo info) @@ -92,37 +190,37 @@ public async Task GenerateAsync(EdgeQLParser.GenerationTargetInfo info) private void CreatedAndUpdated(object sender, FileSystemEventArgs e) { - FileUtils.WaitForHotFile(e.FullPath); + if (!FileUtils.WaitForHotFile(e.FullPath)) + return; + + // wait an extra second to make sure the file is fully written + Thread.Sleep(1000); - var info = EdgeQLParser.GetTargetInfo(e.FullPath, Dir!); + var info = EdgeQLParser.GetTargetInfo(e.FullPath, Output!); if (info.GeneratedTargetExistsAndIsUpToDate()) return; - Task.Run(async () => - { - await Task.Delay(200); - await GenerateAsync(info); - }); + _writeQueue.Push(info); + _writeDispatcher.TrySetResult(); } private void _watcher_Deleted(object sender, FileSystemEventArgs e) { - var info = EdgeQLParser.GetTargetInfo(e.FullPath, Dir!); + // get the generated file name + var path = Path.Combine(Output!, $"{Path.GetFileNameWithoutExtension(e.FullPath)}.g.cs"); - if (File.Exists(info.TargetFilePath)) - File.Delete(info.TargetFilePath); + if (File.Exists(path)) + File.Delete(path); } private void _watcher_Renamed(object sender, RenamedEventArgs e) { - var info = EdgeQLParser.GetTargetInfo(e.OldFullPath, Dir!); + var oldPath = Path.Combine(Output!, $"{Path.GetFileNameWithoutExtension(e.OldFullPath)}.g.cs"); + var newPath = Path.Combine(Output!, $"{Path.GetFileNameWithoutExtension(e.FullPath)}.g.cs"); - if (File.Exists(info.TargetFilePath)) - { - var newInfo = EdgeQLParser.GetTargetInfo(e.FullPath, Dir!); - File.Move(info.TargetFilePath, newInfo.TargetFilePath!); - } + if (File.Exists(oldPath)) + File.Move(oldPath, newPath); } private void _watcher_Error(object sender, ErrorEventArgs e) diff --git a/src/EdgeDB.Net.CLI/Commands/Generate.cs b/src/EdgeDB.Net.CLI/Commands/Generate.cs index 79858802..9d33271c 100644 --- a/src/EdgeDB.Net.CLI/Commands/Generate.cs +++ b/src/EdgeDB.Net.CLI/Commands/Generate.cs @@ -3,6 +3,7 @@ using EdgeDB.CLI.Utils; using EdgeDB.Codecs; using Newtonsoft.Json; +using Serilog; using System.Diagnostics; using System.Reflection; using System.Text.RegularExpressions; @@ -18,7 +19,7 @@ public class Generate : ConnectionArguments, ICommand [Option('o', "output", HelpText = "The output directory for the generated source to be placed.")] public string? OutputDirectory { get; set; } - [Option('n', "project-name", HelpText = "The name of the generated project.")] + [Option('n', "project-name", HelpText = "The name of the generated project and namespace of generated files.")] public string GeneratedProjectName { get; set; } = "EdgeDB.Generated"; [Option('f', "force", HelpText = "Force regeneration of files")] @@ -27,7 +28,7 @@ public class Generate : ConnectionArguments, ICommand [Option("watch", HelpText = "Listens for any changes or new edgeql files and generates them automatically")] public bool Watch { get; set; } - public async Task ExecuteAsync() + public async Task ExecuteAsync(ILogger logger) { // get connection info var connection = GetConnection(); @@ -35,7 +36,7 @@ public async Task ExecuteAsync() // create the client var client = new EdgeDBTcpClient(connection, new()); - Console.WriteLine($"Connecting to {connection.Hostname}:{connection.Port}..."); + logger.Information("Connecting to {@Host}:{@Port}...", connection.Hostname, connection.Port); await client.ConnectAsync(); var projectRoot = ProjectUtils.GetProjectRoot(); @@ -46,7 +47,7 @@ public async Task ExecuteAsync() if (GenerateProject && !Directory.Exists(Path.Combine(OutputDirectory, GeneratedProjectName))) { - Console.WriteLine($"Creating project {GeneratedProjectName}..."); + logger.Information("Creating project {@ProjectName}...", GeneratedProjectName); await ProjectUtils.CreateGeneratedProjectAsync(OutputDirectory, GeneratedProjectName); } @@ -56,7 +57,7 @@ public async Task ExecuteAsync() // find edgeql files var edgeqlFiles = ProjectUtils.GetTargetEdgeQLFiles(projectRoot).ToArray(); - Console.WriteLine($"Generating {edgeqlFiles.Length} files..."); + logger.Information("Generating {@FileCount} files...", edgeqlFiles.Length); for(int i = 0; i != edgeqlFiles.Length; i++) { @@ -65,7 +66,7 @@ public async Task ExecuteAsync() if (!Force && info.GeneratedTargetExistsAndIsUpToDate()) { - Console.WriteLine($"{i + 1}: Skipping {file}: File already generated and up-to-date."); + logger.Warning("Skipping {@File}: File already generated and up-to-date.", file); continue; } @@ -76,17 +77,18 @@ public async Task ExecuteAsync() } catch (EdgeDBErrorException error) { - Console.WriteLine($"Failed to parse {file} (line {error.ErrorResponse.Attributes.FirstOrDefault(x => x.Code == 65523).ToString() ?? "??"}, column {error.ErrorResponse.Attributes.FirstOrDefault(x => x.Code == 65524).ToString() ?? "??"}):"); - Console.WriteLine(error.Message); - Console.WriteLine(QueryErrorFormatter.FormatError(info.EdgeQL!, error)); - Console.WriteLine($"{i + 1}: Skipping {file}: File contains errors"); + logger.Error("Skipping {@File}: Failed to parse - {@Message} at line {@Line} column {@Column}", + file, + error.Message, + error.ErrorResponse.Attributes.FirstOrDefault(x => x.Code == 65523).ToString() ?? "??", + error.ErrorResponse.Attributes.FirstOrDefault(x => x.Code == 65524).ToString() ?? "??"); continue; } - Console.WriteLine($"{i + 1}: {file} => {info.TargetFilePath}"); + logger.Debug("{@EdgeQL} => {@CSharp}", file, info.TargetFilePath); } - Console.WriteLine("Generation complete!"); + logger.Information("Generation complete!"); if(Watch) { @@ -94,31 +96,13 @@ public async Task ExecuteAsync() if(existing is not null) { - Console.WriteLine("Watching already running"); + logger.Warning("Watching already running"); return; } - StartWatchProcess(connection); + logger.Information("Starting file watcher..."); + var pid = ProjectUtils.StartWatchProcess(connection, projectRoot, OutputDirectory, GeneratedProjectName); + logger.Information("File watcher process started, PID: {@PID}", pid); } } - - public void StartWatchProcess(EdgeDBConnection connection) - { - var current = Process.GetCurrentProcess(); - var connString = JsonConvert.SerializeObject(connection).Replace("\"", "\\\""); - - Process.Start(new ProcessStartInfo - { - FileName = current.MainModule.FileName, - Arguments = $"file-watch-internal --connection \"{connString}\" --dir \"{OutputDirectory}\" --namespace \"{GeneratedProjectName}\"", - UseShellExecute = true, - }); - - - - - //process.StartInfo.Arguments = - //process.StartInfo.UseShellExecute = true; - //process.Start(); - } } \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj index 38790c03..bb7d73f4 100644 --- a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj +++ b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj @@ -10,6 +10,8 @@ + + diff --git a/src/EdgeDB.Net.CLI/ICommand.cs b/src/EdgeDB.Net.CLI/ICommand.cs index a9deebb7..7c3d5088 100644 --- a/src/EdgeDB.Net.CLI/ICommand.cs +++ b/src/EdgeDB.Net.CLI/ICommand.cs @@ -1,6 +1,8 @@ -namespace EdgeDB.CLI; +using Serilog; + +namespace EdgeDB.CLI; interface ICommand { - Task ExecuteAsync(); + Task ExecuteAsync(ILogger logger); } \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Program.cs b/src/EdgeDB.Net.CLI/Program.cs index 33882da9..df3058b0 100644 --- a/src/EdgeDB.Net.CLI/Program.cs +++ b/src/EdgeDB.Net.CLI/Program.cs @@ -1,6 +1,13 @@ using CommandLine; using CommandLine.Text; using EdgeDB.CLI; +using EdgeDB.CLI.Arguments; +using Serilog; + +Log.Logger = new LoggerConfiguration() + .MinimumLevel.Verbose() + .WriteTo.Console() + .CreateLogger(); var commands = typeof(Program).Assembly.GetTypes().Where(x => x.GetInterfaces().Any(x => x == typeof(ICommand))); @@ -13,7 +20,18 @@ try { - var commandResult = await result.WithParsedAsync(x => x.ExecuteAsync()); + var commandResult = await result.WithParsedAsync(x => + { + if(x is LogArgs logArgs) + { + Log.Logger = new LoggerConfiguration() + .MinimumLevel.Is(logArgs.LogLevel) + .WriteTo.Console() + .CreateLogger(); + } + + return x.ExecuteAsync(Log.Logger); + }); result.WithNotParsed(err => diff --git a/src/EdgeDB.Net.CLI/Properties/launchSettings.json b/src/EdgeDB.Net.CLI/Properties/launchSettings.json index ee546b5a..a30ce718 100644 --- a/src/EdgeDB.Net.CLI/Properties/launchSettings.json +++ b/src/EdgeDB.Net.CLI/Properties/launchSettings.json @@ -2,7 +2,9 @@ "profiles": { "EdgeDB.Net.CLI": { "commandName": "Project", - "commandLineArgs": "file-watch-internal --dir C:\\Users\\lynch\\source\\repos\\EdgeDB --namespace EdgeDB.Generated --connection \"{\\\"user\\\":\\\"edgedb\\\",\\\"password\\\":\\\"x3WnskHEbmXBvnT1Vf14ABuL\\\",\\\"Hostname\\\":\\\"127.0.0.1\\\",\\\"port\\\":10711,\\\"database\\\":\\\"edgedb\\\",\\\"tls_cert_data\\\":\\\"-----BEGIN CERTIFICATE-----\nMIIC0zCCAbugAwIBAgIRAPQegLaiakFsl0hPzNUeEaAwDQYJKoZIhvcNAQELBQAw\nGDEWMBQGA1UEAwwNRWRnZURCIFNlcnZlcjAeFw0yMjA3MjIxMzA1MzBaFw00MTA5\nMjExMzA1MzBaMBgxFjAUBgNVBAMMDUVkZ2VEQiBTZXJ2ZXIwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDOSRrWTy9oC9J7CWvGKg9Rg7o/R1D4AQO++rML\npSBd1obrSlJB/p5kLWsMuSM29dgRubqqY7PrrjvcDedpDwyAp9JeIz6tIkkqo+/d\n8dRCaV/3wHa5BgfN8fEE6blnmdrQ3asuVafZ989+qoa1PjLc9iU5JJgp/gb/SS7Z\n02L+0y+vw/QjvQcRoiDfCcIfasp6Z2Jor9RCd+AchNG7m0teQ2iK9BXG33QJUXdA\nYwXEALQxJCi23v197xZQUJbXlxh4+YIFJiYA95gXzoB/lBUo/CQSNwxzkofwLtV/\nWT+CmAP25D/v6/rLWi+ps+vM5GFcIetMK0Bk8yzkr+EXB07/AgMBAAGjGDAWMBQG\nA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAUDNnRYoHCtW5\nRMBBf0YTr9zpLA69cK9J6OfY7wPW1ROd3/qmPz57fJ1DOq+5R4U8+rAvk6pO5p6l\nLG3RyQrC5bJuhyxxcBMkc43INC641gMrJlMm4SSRI0HO1nYiZLFdyUjTfrH//ThA\nFVDwAFR/Xw97R1qZQdZYLhxdnRjYIySWUs8yBljtuEB5iorDX9AaXQ25kZ4f8eTE\nsmxezVKQks+FUPdX6yhb++uWvqqzK2flWuiztZmQWLCQnydKuwyBx4izhsiSZ3wL\nbdO8MaTbq2o2dzvzQAWxsf8Wg1Xogep4PA0goquTJvgjzcHuEtyl8404Mb4U/Fpy\ngIFB7zA3bA==\n-----END CERTIFICATE-----\n\\\",\\\"tls_ca\\\":\\\"-----BEGIN CERTIFICATE-----\nMIIC0zCCAbugAwIBAgIRAPQegLaiakFsl0hPzNUeEaAwDQYJKoZIhvcNAQELBQAw\nGDEWMBQGA1UEAwwNRWRnZURCIFNlcnZlcjAeFw0yMjA3MjIxMzA1MzBaFw00MTA5\nMjExMzA1MzBaMBgxFjAUBgNVBAMMDUVkZ2VEQiBTZXJ2ZXIwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDOSRrWTy9oC9J7CWvGKg9Rg7o/R1D4AQO++rML\npSBd1obrSlJB/p5kLWsMuSM29dgRubqqY7PrrjvcDedpDwyAp9JeIz6tIkkqo+/d\n8dRCaV/3wHa5BgfN8fEE6blnmdrQ3asuVafZ989+qoa1PjLc9iU5JJgp/gb/SS7Z\n02L+0y+vw/QjvQcRoiDfCcIfasp6Z2Jor9RCd+AchNG7m0teQ2iK9BXG33QJUXdA\nYwXEALQxJCi23v197xZQUJbXlxh4+YIFJiYA95gXzoB/lBUo/CQSNwxzkofwLtV/\nWT+CmAP25D/v6/rLWi+ps+vM5GFcIetMK0Bk8yzkr+EXB07/AgMBAAGjGDAWMBQG\nA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAUDNnRYoHCtW5\nRMBBf0YTr9zpLA69cK9J6OfY7wPW1ROd3/qmPz57fJ1DOq+5R4U8+rAvk6pO5p6l\nLG3RyQrC5bJuhyxxcBMkc43INC641gMrJlMm4SSRI0HO1nYiZLFdyUjTfrH//ThA\nFVDwAFR/Xw97R1qZQdZYLhxdnRjYIySWUs8yBljtuEB5iorDX9AaXQ25kZ4f8eTE\nsmxezVKQks+FUPdX6yhb++uWvqqzK2flWuiztZmQWLCQnydKuwyBx4izhsiSZ3wL\nbdO8MaTbq2o2dzvzQAWxsf8Wg1Xogep4PA0goquTJvgjzcHuEtyl8404Mb4U/Fpy\ngIFB7zA3bA==\n-----END CERTIFICATE-----\n\\\",\\\"tls_security\\\":0}\"" + "commandLineArgs": "watch -k" + //"commandLineArgs": "watch -start -o C:\\Users\\lynch\\source\\repos\\EdgeDB\\examples\\EdgeDB.Examples.GenerationExample\\EdgeDB.Generated" + //"commandLineArgs": "file-watch-internal --dir C:\\Users\\lynch\\source\\repos\\EdgeDB --output C:\\Users\\lynch\\source\\repos\\EdgeDB\\examples\\EdgeDB.Examples.GenerationExample --namespace EdgeDB.Generated --connection \"{\\\"user\\\":\\\"edgedb\\\",\\\"password\\\":\\\"x3WnskHEbmXBvnT1Vf14ABuL\\\",\\\"Hostname\\\":\\\"127.0.0.1\\\",\\\"port\\\":10711,\\\"database\\\":\\\"edgedb\\\",\\\"tls_cert_data\\\":\\\"-----BEGIN CERTIFICATE-----\nMIIC0zCCAbugAwIBAgIRAPQegLaiakFsl0hPzNUeEaAwDQYJKoZIhvcNAQELBQAw\nGDEWMBQGA1UEAwwNRWRnZURCIFNlcnZlcjAeFw0yMjA3MjIxMzA1MzBaFw00MTA5\nMjExMzA1MzBaMBgxFjAUBgNVBAMMDUVkZ2VEQiBTZXJ2ZXIwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDOSRrWTy9oC9J7CWvGKg9Rg7o/R1D4AQO++rML\npSBd1obrSlJB/p5kLWsMuSM29dgRubqqY7PrrjvcDedpDwyAp9JeIz6tIkkqo+/d\n8dRCaV/3wHa5BgfN8fEE6blnmdrQ3asuVafZ989+qoa1PjLc9iU5JJgp/gb/SS7Z\n02L+0y+vw/QjvQcRoiDfCcIfasp6Z2Jor9RCd+AchNG7m0teQ2iK9BXG33QJUXdA\nYwXEALQxJCi23v197xZQUJbXlxh4+YIFJiYA95gXzoB/lBUo/CQSNwxzkofwLtV/\nWT+CmAP25D/v6/rLWi+ps+vM5GFcIetMK0Bk8yzkr+EXB07/AgMBAAGjGDAWMBQG\nA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAUDNnRYoHCtW5\nRMBBf0YTr9zpLA69cK9J6OfY7wPW1ROd3/qmPz57fJ1DOq+5R4U8+rAvk6pO5p6l\nLG3RyQrC5bJuhyxxcBMkc43INC641gMrJlMm4SSRI0HO1nYiZLFdyUjTfrH//ThA\nFVDwAFR/Xw97R1qZQdZYLhxdnRjYIySWUs8yBljtuEB5iorDX9AaXQ25kZ4f8eTE\nsmxezVKQks+FUPdX6yhb++uWvqqzK2flWuiztZmQWLCQnydKuwyBx4izhsiSZ3wL\nbdO8MaTbq2o2dzvzQAWxsf8Wg1Xogep4PA0goquTJvgjzcHuEtyl8404Mb4U/Fpy\ngIFB7zA3bA==\n-----END CERTIFICATE-----\n\\\",\\\"tls_ca\\\":\\\"-----BEGIN CERTIFICATE-----\nMIIC0zCCAbugAwIBAgIRAPQegLaiakFsl0hPzNUeEaAwDQYJKoZIhvcNAQELBQAw\nGDEWMBQGA1UEAwwNRWRnZURCIFNlcnZlcjAeFw0yMjA3MjIxMzA1MzBaFw00MTA5\nMjExMzA1MzBaMBgxFjAUBgNVBAMMDUVkZ2VEQiBTZXJ2ZXIwggEiMA0GCSqGSIb3\nDQEBAQUAA4IBDwAwggEKAoIBAQDOSRrWTy9oC9J7CWvGKg9Rg7o/R1D4AQO++rML\npSBd1obrSlJB/p5kLWsMuSM29dgRubqqY7PrrjvcDedpDwyAp9JeIz6tIkkqo+/d\n8dRCaV/3wHa5BgfN8fEE6blnmdrQ3asuVafZ989+qoa1PjLc9iU5JJgp/gb/SS7Z\n02L+0y+vw/QjvQcRoiDfCcIfasp6Z2Jor9RCd+AchNG7m0teQ2iK9BXG33QJUXdA\nYwXEALQxJCi23v197xZQUJbXlxh4+YIFJiYA95gXzoB/lBUo/CQSNwxzkofwLtV/\nWT+CmAP25D/v6/rLWi+ps+vM5GFcIetMK0Bk8yzkr+EXB07/AgMBAAGjGDAWMBQG\nA1UdEQQNMAuCCWxvY2FsaG9zdDANBgkqhkiG9w0BAQsFAAOCAQEAUDNnRYoHCtW5\nRMBBf0YTr9zpLA69cK9J6OfY7wPW1ROd3/qmPz57fJ1DOq+5R4U8+rAvk6pO5p6l\nLG3RyQrC5bJuhyxxcBMkc43INC641gMrJlMm4SSRI0HO1nYiZLFdyUjTfrH//ThA\nFVDwAFR/Xw97R1qZQdZYLhxdnRjYIySWUs8yBljtuEB5iorDX9AaXQ25kZ4f8eTE\nsmxezVKQks+FUPdX6yhb++uWvqqzK2flWuiztZmQWLCQnydKuwyBx4izhsiSZ3wL\nbdO8MaTbq2o2dzvzQAWxsf8Wg1Xogep4PA0goquTJvgjzcHuEtyl8404Mb4U/Fpy\ngIFB7zA3bA==\n-----END CERTIFICATE-----\n\\\",\\\"tls_security\\\":0}\"" //"commandLineArgs": "generate -o C:\\Users\\lynch\\source\\repos\\EdgeDB\\examples\\EdgeDB.Examples.GenerationExample --watch" } } diff --git a/src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs b/src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs deleted file mode 100644 index 8085d0e4..00000000 --- a/src/EdgeDB.Net.CLI/Utils/EdgeDBColorer.cs +++ /dev/null @@ -1,455 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.RegularExpressions; -using System.Threading.Tasks; - -namespace EdgeDB.CLI.Utils -{ - internal class EdgeDBColorer - { - public const int ANSI_COLOR_RED = 31; - public const int ANSI_COLOR_GREEN = 32; - public const int ANSI_COLOR_YELLOW = 33; - public const int ANSI_COLOR_BLUE = 34; - public const int ANSI_COLOR_PINK = 35; - public const int ANSI_COLOR_WHITE = 37; - - public static string ColorSchemaOrQuery(string edgeql, Range ignore) - { - var lIndent = 0; - var left = edgeql[..ignore.Start]; - var right = edgeql[ignore.End..]; - var unmarkedContent = edgeql[ignore]; - return ColorSchemaOrQuery(left, ref lIndent) + unmarkedContent + ColorSchemaOrQuery(right, ref lIndent); - } - - public static string ColorSchemaOrQuery(string edgeql) - { - int i = 0; - return ColorSchemaOrQuery(edgeql, ref i); - } - - public static string ColorSchemaOrQuery(string edgeql, ref int indentLevel) - { - var mutableSchema = edgeql; - var coloredSchema = ""; - var currentColor = 0; - - void ChangeColor(int color) - { - if (currentColor == color) - return; - - coloredSchema += $"\u001b[0;{color}m"; - currentColor = color; - } - - while (!string.IsNullOrEmpty(mutableSchema)) - { - var word = new string(mutableSchema.TakeWhile(x => !(x is '<' or '>' or '\"' or '\'' or ',' or '{' or '}' or '[' or ']' or ' ' or '(' or ')' or ';' or ':')).ToArray()); - - if (string.IsNullOrEmpty(word)) - word = $"{mutableSchema.FirstOrDefault('\u0000')}"; - - if (word == "\u0000") - break; - - mutableSchema = mutableSchema[(word.Length)..]; - - var lower = word.ToLower(); - - if(Regex.IsMatch(word, @"^\d+$")) - ChangeColor(ANSI_COLOR_GREEN); - else if (word == "<") - { - int d = 0; - var str = new string(mutableSchema.TakeWhile((c, i) => - { - if (c == '<') - d++; - else if (c == '>' && d == 0) - return false; - else if (c == '>') - d--; - return true; - - }).ToArray()); - word += str + mutableSchema[str.Length]; - ChangeColor(ANSI_COLOR_RED); - mutableSchema = mutableSchema[(word.Length - 1)..]; - } - else if (word == "\"") - { - var str = new string(mutableSchema.TakeWhile((c, i) => !(c is '\"' && mutableSchema[i - 1] is not '\\')).ToArray()); - word += str + mutableSchema[str.Length]; - ChangeColor(ANSI_COLOR_YELLOW); - mutableSchema = mutableSchema[(word.Length - 1)..]; - } - else if (word == "\'") - { - var str = new string(mutableSchema.TakeWhile((c, i) => !(c is '\'' && mutableSchema[i - 1] is not '\\')).ToArray()); - word += str + mutableSchema[str.Length]; - ChangeColor(ANSI_COLOR_YELLOW); - mutableSchema = mutableSchema[(word.Length - 1)..]; - } - else if (word == " " || word == "\n") { } - else if (word is "{" or "}" or "(" or ")") - { - if (word is "{" or "(") - indentLevel++; - - switch (indentLevel % 3) - { - case 1: - ChangeColor(ANSI_COLOR_YELLOW); - break; - case 2: - ChangeColor(ANSI_COLOR_PINK); - break; - default: - ChangeColor(ANSI_COLOR_BLUE); - break; - } - - if (word is "}" or ")") - indentLevel--; - } - else if (word is ")" or "(") - ChangeColor(ANSI_COLOR_BLUE); - else if (ReservedKeywords.Contains(lower) || Unreservedkeywords.Contains(lower) || BoolLiterals.Contains(lower)) - ChangeColor(ANSI_COLOR_BLUE); - else if (TypesBuiltIns.Contains(lower)) - ChangeColor(ANSI_COLOR_GREEN); - else if (ConstraintsBuiltIns.Contains(lower) || FunctionsBuiltIns.Contains(lower)) - ChangeColor(ANSI_COLOR_YELLOW); - else - ChangeColor(ANSI_COLOR_WHITE); - - coloredSchema += $"{word}"; - } - - return coloredSchema; - } - - private static string[] ReservedKeywords = new string[] - { - "__source__", - "__std__", - "__subject__", - "__type__", - "abort", - "alter", - "analyze", - "and", - "anyarray", - "anytuple", - "anytype", - "begin", - "case", - "check", - "commit", - "configure", - "constraint", - "create", - "deallocate", - "declare", - "delete", - "describe", - "detached", - "discard", - "distinct", - "do", - "drop", - "else", - "empty", - "end", - "execute", - "exists", - "explain", - "extending", - "fetch", - "filter", - "for", - "get", - "global", - "grant", - "group", - "if", - "ilike", - "import", - "in", - "insert", - "introspect", - "is", - "like", - "limit", - "listen", - "load", - "lock", - "match", - "module", - "move", - "not", - "notify", - "offset", - "optional", - "or", - "order", - "over", - "partition", - "policy", - "populate", - "prepare", - "raise", - "refresh", - "reindex", - "release", - "reset", - "revoke", - "rollback", - "select", - "set", - "start", - "typeof", - "union", - "update", - "variadic", - "when", - "window", - "with", - }; - - private static string[] Unreservedkeywords = new string[] - { - "abstract", - "after", - "alias", - "all", - "allow", - "annotation", - "as", - "asc", - "assignment", - "before", - "by", - "cardinality", - "cast", - "config", - "conflict", - "constraint", - "current", - "database", - "ddl", - "default", - "deferrable", - "deferred", - "delegated", - "desc", - "emit", - "explicit", - "expression", - "final", - "first", - "from", - "function", - "implicit", - "index", - "infix", - "inheritable", - "into", - "isolation", - "json", - "last", - "link", - "migration", - "multi", - "named", - "object", - "of", - "oids", - "on", - "only", - "onto", - "operator", - "overloaded", - "owned", - "postfix", - "prefix", - "property", - "proposed", - "pseudo", - "read", - "reject", - "rename", - "repeatable", - "required", - "restrict", - "role", - "roles", - "savepoint", - "scalar", - "schema", - "sdl", - "serializable", - "session", - "single", - "source", - "superuser", - "system", - "target", - "ternary", - "text", - "then", - "to", - "transaction", - "type", - "unless", - "using", - "verbose", - "view", - "write", - }; - - private static string[] BoolLiterals = new string[] - { - "false", - "true", - }; - - private static string[] TypesBuiltIns = new string[] - { - "BaseObject", - "Object", - "anyenum", - "anyfloat", - "anyint", - "anynumeric", - "anyreal", - "anyscalar", - "array", - "bigint", - "bool", - "bytes", - "datetime", - "decimal", - "duration", - "enum", - "float32", - "float64", - "int16", - "int32", - "int64", - "json", - "local_date", - "local_datetime", - "local_time", - "sequence", - "str", - "tuple", - "uuid", - }; - - private static string[] ConstraintsBuiltIns = new string[] - { - "exclusive", - "expression", - "len_value", - "max_ex_value", - "max_len_value", - "max_value", - "min_ex_value", - "min_len_value", - "min_value", - "one_of", - "regexp", - }; - - private static string[] FunctionsBuiltIns = new string[] - { - "abs", - "advisory_lock", - "advisory_unlock", - "advisory_unlock_all", - "all", - "any", - "array_agg", - "array_get", - "array_join", - "array_unpack", - "bytes_get_bit", - "ceil", - "contains", - "count", - "date_get", - "datetime_current", - "datetime_get", - "datetime_of_statement", - "datetime_of_transaction", - "datetime_truncate", - "duration_to_seconds", - "duration_truncate", - "enumerate", - "find", - "floor", - "get_current_database", - "get_transaction_isolation", - "get_version", - "get_version_as_str", - "json_array_unpack", - "json_get", - "json_object_unpack", - "json_typeof", - "len", - "lg", - "ln", - "log", - "max", - "mean", - "min", - "random", - "re_match", - "re_match_all", - "re_replace", - "re_test", - "round", - "sleep", - "stddev", - "stddev_pop", - "str_lower", - "str_lpad", - "str_ltrim", - "str_pad_end", - "str_pad_start", - "str_repeat", - "str_rpad", - "str_rtrim", - "str_split", - "str_title", - "str_trim", - "str_trim_end", - "str_trim_start", - "str_upper", - "sum", - "time_get", - "to_bigint", - "to_datetime", - "to_decimal", - "to_duration", - "to_float32", - "to_float64", - "to_int16", - "to_int32", - "to_int64", - "to_json", - "to_local_date", - "to_local_datetime", - "to_local_time", - "to_str", - "uuid_generate_v1mc", - "var", - "var_pop", - }; - } -} diff --git a/src/EdgeDB.Net.CLI/Utils/FileUtils.cs b/src/EdgeDB.Net.CLI/Utils/FileUtils.cs index 8634d194..e4cf2693 100644 --- a/src/EdgeDB.Net.CLI/Utils/FileUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/FileUtils.cs @@ -4,7 +4,7 @@ using System.Text; using System.Threading.Tasks; -namespace EdgeDB.Net.CLI.Utils +namespace EdgeDB.CLI.Utils { internal class FileUtils { diff --git a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs index 3f606e61..fe59b0fe 100644 --- a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs @@ -1,4 +1,5 @@ using CliWrap; +using Newtonsoft.Json; using System; using System.Collections.Generic; using System.Diagnostics; @@ -26,6 +27,19 @@ public static string GetProjectRoot() return directory; } + public static int StartWatchProcess(EdgeDBConnection connection, string root, string outputDir, string @namespace) + { + var current = Process.GetCurrentProcess(); + var connString = JsonConvert.SerializeObject(connection).Replace("\"", "\\\""); + + return Process.Start(new ProcessStartInfo + { + FileName = current.MainModule!.FileName, + Arguments = $"file-watch-internal --connection \"{connString}\" --dir {root} --output \"{outputDir}\" --namespace \"{@namespace}\"", + UseShellExecute = true, + })!.Id; + } + public static Process? GetWatcherProcess(string root) { var file = Path.Combine(root, "edgeql.dotnet.watcher.process"); @@ -33,7 +47,7 @@ public static string GetProjectRoot() { try { - return Process.GetProcessById(id); + return Process.GetProcesses().FirstOrDefault(x => x.Id == id); } catch { return null; } } diff --git a/src/EdgeDB.Net.CLI/Utils/QueryErrorFormatter.cs b/src/EdgeDB.Net.CLI/Utils/QueryErrorFormatter.cs deleted file mode 100644 index 6b21dda6..00000000 --- a/src/EdgeDB.Net.CLI/Utils/QueryErrorFormatter.cs +++ /dev/null @@ -1,37 +0,0 @@ -using EdgeDB; -using EdgeDB.Binary; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.CLI.Utils -{ - internal class QueryErrorFormatter - { - public static string FormatError(string query, EdgeDBErrorException error) - { - var headers = error.ErrorResponse.Attributes.Cast(); - var rawCharStart = headers.FirstOrDefault(x => x.HasValue && x.Value.Code == 0xFFF9, null); - var rawCharEnd = headers.FirstOrDefault(x => x.HasValue && x.Value.Code == 0xFFFA, null); - - if (!rawCharStart.HasValue || !rawCharEnd.HasValue) - return EdgeDBColorer.ColorSchemaOrQuery(query); - - int charStart = int.Parse(rawCharStart.Value.ToString()), - charEnd = int.Parse(rawCharEnd.Value.ToString()); - - var queryErrorSource = query[charStart..charEnd]; - var count = charEnd - charStart; - - var coloredQuery = EdgeDBColorer.ColorSchemaOrQuery(query, charStart..charEnd); - - // make the error section red - var coloredIndex = coloredQuery.IndexOf(queryErrorSource, charStart); - coloredQuery = coloredQuery.Remove(coloredIndex, count); - coloredQuery = coloredQuery.Insert(coloredIndex, $"\u001b[0;31m{queryErrorSource}"); - return coloredQuery + "\u001b[0m"; - } - } -} From c06efa13f33b4747b1077eb8ea7321ca984b5168 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 28 Aug 2022 09:58:56 -0300 Subject: [PATCH 07/13] watcher ignores migration files --- src/EdgeDB.Net.CLI/Arguments/LogArgs.cs | 2 +- src/EdgeDB.Net.CLI/Commands/FileWatch.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs b/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs index 243dbe34..f1062d56 100644 --- a/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs +++ b/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs @@ -11,7 +11,7 @@ namespace EdgeDB.CLI.Arguments { public class LogArgs { - [Option("loglevel")] + [Option("loglevel", HelpText = "Configure the log level")] public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; } } diff --git a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs index 578fe6c8..81d247d2 100644 --- a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs +++ b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs @@ -159,12 +159,19 @@ public async Task ExecuteAsync(ILogger logger) } } + private bool IsValidFile(string path) + => !path.StartsWith(Path.Combine(Dir!, "dbschema", "migrations")); + public async Task GenerateAsync(EdgeQLParser.GenerationTargetInfo info) { await _mutex.WaitAsync().ConfigureAwait(false); try { + // check if the file is a valid file + if (!IsValidFile(info.EdgeQLFilePath!)) + return; + await _client!.ConnectAsync(); try @@ -207,6 +214,9 @@ private void CreatedAndUpdated(object sender, FileSystemEventArgs e) private void _watcher_Deleted(object sender, FileSystemEventArgs e) { + if (!IsValidFile(e.FullPath)) + return; + // get the generated file name var path = Path.Combine(Output!, $"{Path.GetFileNameWithoutExtension(e.FullPath)}.g.cs"); @@ -216,6 +226,9 @@ private void _watcher_Deleted(object sender, FileSystemEventArgs e) private void _watcher_Renamed(object sender, RenamedEventArgs e) { + if (!IsValidFile(e.FullPath)) + return; + var oldPath = Path.Combine(Output!, $"{Path.GetFileNameWithoutExtension(e.OldFullPath)}.g.cs"); var newPath = Path.Combine(Output!, $"{Path.GetFileNameWithoutExtension(e.FullPath)}.g.cs"); From f976572a48d91faa4073e61f0a5f4995484c98f4 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 28 Aug 2022 10:02:36 -0300 Subject: [PATCH 08/13] package metadata --- src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj index bb7d73f4..8e0f57fb 100644 --- a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj +++ b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj @@ -1,10 +1,15 @@ - + Exe net6.0 enable - enable + enable + EdgeDB.Net.CLI + EdgeDB + A CLI tool to generate C# files from edgeql files + edgeql + true From f88e8788f02404854bde26df55e259c9be065597 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 28 Aug 2022 10:25:20 -0300 Subject: [PATCH 09/13] update help text --- src/EdgeDB.Net.CLI/Commands/FileWatch.cs | 2 +- src/EdgeDB.Net.CLI/Commands/Generate.cs | 8 ++++---- src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs | 1 - 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs index 81d247d2..46540994 100644 --- a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs +++ b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs @@ -75,7 +75,7 @@ public Task ExecuteAsync(ILogger logger) var connection = GetConnection(); - OutputDirectory ??= Path.Combine(root, GeneratedProjectName); + OutputDirectory ??= Path.Combine(Environment.CurrentDirectory, GeneratedProjectName); var pid = ProjectUtils.StartWatchProcess(connection, root, OutputDirectory, GeneratedProjectName); diff --git a/src/EdgeDB.Net.CLI/Commands/Generate.cs b/src/EdgeDB.Net.CLI/Commands/Generate.cs index 9d33271c..c2561543 100644 --- a/src/EdgeDB.Net.CLI/Commands/Generate.cs +++ b/src/EdgeDB.Net.CLI/Commands/Generate.cs @@ -13,10 +13,10 @@ namespace EdgeDB.CLI; [Verb("generate", HelpText = "Generate or updates csharp classes from .edgeql files.")] public class Generate : ConnectionArguments, ICommand { - [Option('p', "build-project", HelpText = "Whether or not to create the default class library that will contain the generated source code.")] + [Option('p', "build-project", HelpText = "Whether or not to create the default class library that will contain the generated source code. Enabled by default.")] public bool GenerateProject { get; set; } = true; - [Option('o', "output", HelpText = "The output directory for the generated source to be placed.")] + [Option('o', "output", HelpText = "The output directory for the generated source to be placed. When generating a project, source files will be placed in that projects directory. Default is the current directory")] public string? OutputDirectory { get; set; } [Option('n', "project-name", HelpText = "The name of the generated project and namespace of generated files.")] @@ -25,7 +25,7 @@ public class Generate : ConnectionArguments, ICommand [Option('f', "force", HelpText = "Force regeneration of files")] public bool Force { get; set; } - [Option("watch", HelpText = "Listens for any changes or new edgeql files and generates them automatically")] + [Option("watch", HelpText = "Listens for any changes or new edgeql files and (re)generates them automatically")] public bool Watch { get; set; } public async Task ExecuteAsync(ILogger logger) @@ -41,7 +41,7 @@ public async Task ExecuteAsync(ILogger logger) var projectRoot = ProjectUtils.GetProjectRoot(); - OutputDirectory ??= projectRoot; + OutputDirectory ??= Environment.CurrentDirectory; Directory.CreateDirectory(OutputDirectory); diff --git a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs index fe59b0fe..17c9ff33 100644 --- a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs @@ -73,7 +73,6 @@ public static void RegisterProcessAsWatcher(string root) File.WriteAllText(gitignore, contents); } } - } public static async Task CreateGeneratedProjectAsync(string root, string name) From a5d3b56798cee4aafa072c160654822c41b85808 Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 28 Aug 2022 10:26:24 -0300 Subject: [PATCH 10/13] make watcher subprocess gui hidden --- src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs index 17c9ff33..da1abfa9 100644 --- a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs @@ -37,6 +37,8 @@ public static int StartWatchProcess(EdgeDBConnection connection, string root, st FileName = current.MainModule!.FileName, Arguments = $"file-watch-internal --connection \"{connString}\" --dir {root} --output \"{outputDir}\" --namespace \"{@namespace}\"", UseShellExecute = true, + CreateNoWindow = true, + WindowStyle = ProcessWindowStyle.Hidden })!.Id; } @@ -57,7 +59,7 @@ public static int StartWatchProcess(EdgeDBConnection connection, string root, st public static void RegisterProcessAsWatcher(string root) { - var id = Process.GetCurrentProcess().Id; + var id = Environment.ProcessId; File.WriteAllText(Path.Combine(root, "edgeql.dotnet.watcher.process"), $"{id}"); From 3c7e070264658c987ee94e4704784ab460a99cbc Mon Sep 17 00:00:00 2001 From: Quin Lynch Date: Sun, 28 Aug 2022 10:29:33 -0300 Subject: [PATCH 11/13] remove tabs from project file --- src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj index 8e0f57fb..cfc8deef 100644 --- a/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj +++ b/src/EdgeDB.Net.CLI/EdgeDB.Net.CLI.csproj @@ -4,11 +4,11 @@ Exe net6.0 enable - enable - EdgeDB.Net.CLI - EdgeDB - A CLI tool to generate C# files from edgeql files - edgeql + enable + EdgeDB.Net.CLI + EdgeDB + A CLI tool to generate C# files from edgeql files + edgeql true From 80e12ef9ca6bdd6715ec7c6dd1e417c814310648 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:14:10 -0700 Subject: [PATCH 12/13] document code --- .../Properties/launchSettings.json | 5 +- src/EdgeDB.Net.CLI/Arguments/LogArgs.cs | 6 + src/EdgeDB.Net.CLI/Commands/FileWatch.cs | 18 +- src/EdgeDB.Net.CLI/Commands/Generate.cs | 23 +- src/EdgeDB.Net.CLI/EdgeQLParser.cs | 237 ++++++++++++++++-- src/EdgeDB.Net.CLI/ErrorHandler.cs | 36 --- src/EdgeDB.Net.CLI/ICommand.cs | 10 + src/EdgeDB.Net.CLI/Program.cs | 19 +- src/EdgeDB.Net.CLI/Utils/CodeWriter.cs | 74 +++++- src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs | 9 + src/EdgeDB.Net.CLI/Utils/FileUtils.cs | 11 +- src/EdgeDB.Net.CLI/Utils/HashUtils.cs | 10 +- src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs | 47 +++- src/EdgeDB.Net.CLI/Utils/TextUtils.cs | 18 +- 14 files changed, 435 insertions(+), 88 deletions(-) delete mode 100644 src/EdgeDB.Net.CLI/ErrorHandler.cs diff --git a/examples/EdgeDB.Examples.ExampleTODOApi/Properties/launchSettings.json b/examples/EdgeDB.Examples.ExampleTODOApi/Properties/launchSettings.json index 9f782db0..0999bbcb 100644 --- a/examples/EdgeDB.Examples.ExampleTODOApi/Properties/launchSettings.json +++ b/examples/EdgeDB.Examples.ExampleTODOApi/Properties/launchSettings.json @@ -1,4 +1,4 @@ -{ +{ "$schema": "https://json.schemastore.org/launchsettings.json", "iisSettings": { "windowsAuthentication": false, @@ -11,7 +11,6 @@ "profiles": { "EdgeDB.Examples.ExampleTODOApi": { "commandName": "Project", - "dotnetRunMessages": true, "launchBrowser": true, "launchUrl": "swagger", "applicationUrl": "https://localhost:7155;http://localhost:5155", @@ -28,4 +27,4 @@ } } } -} +} \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs b/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs index f1062d56..b72ed64a 100644 --- a/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs +++ b/src/EdgeDB.Net.CLI/Arguments/LogArgs.cs @@ -9,8 +9,14 @@ namespace EdgeDB.CLI.Arguments { + /// + /// A class containing logger arguments. + /// public class LogArgs { + /// + /// Gets or sets the log level for the default logger. + /// [Option("loglevel", HelpText = "Configure the log level")] public LogEventLevel LogLevel { get; set; } = LogEventLevel.Information; } diff --git a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs index 46540994..c7ec55cf 100644 --- a/src/EdgeDB.Net.CLI/Commands/FileWatch.cs +++ b/src/EdgeDB.Net.CLI/Commands/FileWatch.cs @@ -13,21 +13,37 @@ namespace EdgeDB.CLI.Commands { + /// + /// A class representing the watch command. + /// [Verb("watch", HelpText = "Configure the file watcher")] public class FileWatch : ConnectionArguments, ICommand { + /// + /// Gets or sets whether or not to kill a already running watcher. + /// [Option('k', "kill", SetName = "functions", HelpText = "Kill the current running watcher for the project")] public bool Kill { get; set; } + /// + /// Gets or sets whether or not to start a watcher. + /// [Option('s', "start", SetName = "functions", HelpText = "Start a watcher for the current project")] public bool Start { get; set; } + /// + /// Gets or sets the output directory to place the generated source files. + /// [Option('o', "output", HelpText = "The output directory for the generated source to be placed.")] public string? OutputDirectory { get; set; } + /// + /// Gets or sets the project name/namespace of generated files. + /// [Option('n', "project-name", HelpText = "The name of the generated project and namespace of generated files.")] public string GeneratedProjectName { get; set; } = "EdgeDB.Generated"; + /// public Task ExecuteAsync(ILogger logger) { // get project root @@ -205,7 +221,7 @@ private void CreatedAndUpdated(object sender, FileSystemEventArgs e) var info = EdgeQLParser.GetTargetInfo(e.FullPath, Output!); - if (info.GeneratedTargetExistsAndIsUpToDate()) + if (info.IsGeneratedTargetExistsAndIsUpToDate()) return; _writeQueue.Push(info); diff --git a/src/EdgeDB.Net.CLI/Commands/Generate.cs b/src/EdgeDB.Net.CLI/Commands/Generate.cs index c2561543..fdf608f0 100644 --- a/src/EdgeDB.Net.CLI/Commands/Generate.cs +++ b/src/EdgeDB.Net.CLI/Commands/Generate.cs @@ -10,24 +10,43 @@ namespace EdgeDB.CLI; +/// +/// A class representing the generate command. +/// [Verb("generate", HelpText = "Generate or updates csharp classes from .edgeql files.")] public class Generate : ConnectionArguments, ICommand { - [Option('p', "build-project", HelpText = "Whether or not to create the default class library that will contain the generated source code. Enabled by default.")] + /// + /// Gets or sets whether or not a class library should be generated. + /// + [Option('p', "project", HelpText = "Whether or not to create the default class library that will contain the generated source code. Enabled by default.")] public bool GenerateProject { get; set; } = true; + /// + /// Gets or sets the output directory the generated source files will be placed. + /// [Option('o', "output", HelpText = "The output directory for the generated source to be placed. When generating a project, source files will be placed in that projects directory. Default is the current directory")] public string? OutputDirectory { get; set; } + /// + /// Gets or sets the project name/namespace. + /// [Option('n', "project-name", HelpText = "The name of the generated project and namespace of generated files.")] public string GeneratedProjectName { get; set; } = "EdgeDB.Generated"; + /// + /// Gets or sets whether or not to force (re)generate source files. + /// [Option('f', "force", HelpText = "Force regeneration of files")] public bool Force { get; set; } + /// + /// Gets or sets whether or not to start a watch process post-generate. + /// [Option("watch", HelpText = "Listens for any changes or new edgeql files and (re)generates them automatically")] public bool Watch { get; set; } + /// public async Task ExecuteAsync(ILogger logger) { // get connection info @@ -64,7 +83,7 @@ public async Task ExecuteAsync(ILogger logger) var file = edgeqlFiles[i]; var info = EdgeQLParser.GetTargetInfo(file, OutputDirectory); - if (!Force && info.GeneratedTargetExistsAndIsUpToDate()) + if (!Force && info.IsGeneratedTargetExistsAndIsUpToDate()) { logger.Warning("Skipping {@File}: File already generated and up-to-date.", file); continue; diff --git a/src/EdgeDB.Net.CLI/EdgeQLParser.cs b/src/EdgeDB.Net.CLI/EdgeQLParser.cs index b44e3352..72c761f5 100644 --- a/src/EdgeDB.Net.CLI/EdgeQLParser.cs +++ b/src/EdgeDB.Net.CLI/EdgeQLParser.cs @@ -10,10 +10,29 @@ namespace EdgeDB.CLI { + /// + /// Represents a class responsible for parsing and transpiling edgeql to C#. + /// internal class EdgeQLParser { + /// + /// The file header regex for generate C# files. + /// private static readonly Regex _headerHashRegex = new(@"\/\/ edgeql:([0-9a-fA-F]{64})"); + /// + /// Parses and generates a from a given client and + /// . + /// + /// The client to preform the parse with. + /// The namespace for the . + /// + /// The information containimg the edgeql and related + /// content used to parse and generate. + /// + /// A containing the generated C# code, hash, + /// classname, and executer name. + /// public static async Task ParseAndGenerateAsync(EdgeDBTcpClient client, string @namespace, GenerationTargetInfo targetInfo) { var parseResult = await client.ParseAsync(targetInfo.EdgeQL!, Cardinality.Many, IOFormat.Binary, Capabilities.All, default); @@ -21,6 +40,14 @@ public static async Task ParseAndGenerateAsync(EdgeDBTcpClient return GenerateCSharpFromEdgeQL(@namespace, targetInfo, parseResult); } + /// + /// Checks whether an autogenerate header matches a hash. + /// + /// The header of the autogenerated file to check against. + /// The hash to check. + /// + /// if the header matches the hash; otherwise . + /// public static bool TargetFileHashMatches(string header, string hash) { var match = _headerHashRegex.Match(header); @@ -29,31 +56,70 @@ public static bool TargetFileHashMatches(string header, string hash) return match.Groups[1].Value == hash; } - public static GenerationTargetInfo GetTargetInfo(string edgeqlFile, string targetDir) + /// + /// Gets the for the given file and + /// generation target directory. + /// + /// + /// This operation requires the file to be opened. This function will + /// throw if the file is being used by a different process. + /// + /// The path of the edgeql file. + /// The output target directory. + /// + /// The for the given file. + /// + public static GenerationTargetInfo GetTargetInfo(string edgeqlFilePath, string targetDir) { - string fileContent = File.ReadAllText(edgeqlFile); + string fileContent = File.ReadAllText(edgeqlFilePath); var hash = HashUtils.HashEdgeQL(fileContent); - var fileName = TextUtils.ToPascalCase(Path.GetFileName(edgeqlFile).Split('.')[0]); + var fileName = TextUtils.ToPascalCase(Path.GetFileName(edgeqlFilePath).Split('.')[0]); return new GenerationTargetInfo { - FileNameWithoutExtension = fileName, + EdgeQLFileNameWithoutExtension = fileName, EdgeQL = fileContent, EdgeQLHash = hash, - EdgeQLFilePath = edgeqlFile, + EdgeQLFilePath = edgeqlFilePath, TargetFilePath = Path.Combine(targetDir, $"{fileName}.g.cs") }; } + /// + /// Represents a generation target, containing useful information for it. + /// public class GenerationTargetInfo { - public string? FileNameWithoutExtension { get; set; } + /// + /// Gets or sets the edgeql file name without extension. + /// + public string? EdgeQLFileNameWithoutExtension { get; set; } + + /// + /// Gets or sets the edgeql file path. + /// public string? EdgeQLFilePath { get; set; } + + /// + /// Gets or sets the output target file path. + /// public string? TargetFilePath { get; set; } + + /// + /// Gets or sets the edgeql. + /// public string? EdgeQL { get; set; } + + /// + /// Gets or sets the hash of the edgeql. + /// public string? EdgeQLHash { get; set; } - public bool GeneratedTargetExistsAndIsUpToDate() + /// + /// Checks if the target file exists and the header matches the hash of the edgeql. + /// + /// + public bool IsGeneratedTargetExistsAndIsUpToDate() { var lines = File.Exists(TargetFilePath) ? File.ReadAllLines(TargetFilePath) : Array.Empty(); @@ -61,18 +127,49 @@ public bool GeneratedTargetExistsAndIsUpToDate() } } + /// + /// A class representing the result of a edgeql -> cs generation operation. + /// public class GenerationResult { + /// + /// Gets the code generated from edgeql. + /// public string? Code { get; set; } + + /// + /// Gets the hash of the edgeql and header of the generated code. + /// public string? EdgeQLHash { get; set; } + + /// + /// Gets the name of the class containing the execute method. + /// public string? ExecuterClassName { get; set; } + + /// + /// Gets the name of the return result that the executer method returns. + /// public string? ReturnResult { get; set; } + + /// + /// Gets a collection of parameters (edgeql arguments) for the executer function. + /// public IEnumerable? Parameters { get; set; } } + /// + /// Generates a from the given + /// and . + /// + /// The namepsace for the generated code to consume. + /// The used for generation. + /// The parse result from edgedb. + /// + /// private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, GenerationTargetInfo targetInfo, EdgeDBBinaryClient.ParseResult parseResult) { - var codecType = GetTypeOfCodec(parseResult.OutCodec.Codec, $"{targetInfo.FileNameWithoutExtension} Result"); + var codecType = GetTypeInfoFromCodec(parseResult.OutCodec.Codec, $"{targetInfo.EdgeQLFileNameWithoutExtension} Result"); // create the class writer @@ -128,7 +225,7 @@ private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, Gene } // create the executor class - var classScope = writer.BeginScope($"public static class {targetInfo.FileNameWithoutExtension}"); + var classScope = writer.BeginScope($"public static class {targetInfo.EdgeQLFileNameWithoutExtension}"); writer.AppendLine($"public static readonly string Query = @\"{targetInfo.EdgeQL}\";"); writer.AppendLine(); @@ -156,7 +253,7 @@ private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, Gene } else if (parseResult.InCodec.Codec is Codecs.Object argCodec) { - argParameters = argParameters = argCodec.PropertyNames.Select((x, i) => BuildTypes(GetTypeOfCodec(argCodec.InnerCodecs[i], x), out _, namesOnScalar: true, camelCase: true)); + argParameters = argParameters = argCodec.PropertyNames.Select((x, i) => BuildTypes(GetTypeInfoFromCodec(argCodec.InnerCodecs[i], x), out _, namesOnScalar: true, camelCase: true)); methodArgs = methodArgs = argCodec.PropertyNames.Select((x, i) => { return $"{{ \"{x}\", {TextUtils.ToCamelCase(x)} }}"; @@ -169,7 +266,7 @@ private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, Gene writer.AppendLine($" => client.{method}<{typeName ?? mainResult}>(Query{(methodArgs.Any() ? $", new Dictionary() {{ {string.Join(", ", methodArgs)} }}" : "")}, capabilities: (Capabilities){(ulong)parseResult.Capabilities}ul, token: token);"); writer.AppendLine(); - writer.AppendLine($"public static Task<{resultType}> {targetInfo.FileNameWithoutExtension}Async(this IEdgeDBQueryable client{(argParameters.Any() ? $", {string.Join(", ", argParameters)}" : "")}, CancellationToken token = default)"); + writer.AppendLine($"public static Task<{resultType}> {targetInfo.EdgeQLFileNameWithoutExtension}Async(this IEdgeDBQueryable client{(argParameters.Any() ? $", {string.Join(", ", argParameters)}" : "")}, CancellationToken token = default)"); writer.AppendLine($" => ExecuteAsync(client{(argParameters.Any() ? $", {string.Join(", ", argParameters.Select(x => x.Split(' ')[1]))}" : "")}, token: token);"); classScope.Dispose(); @@ -178,7 +275,7 @@ private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, Gene return new() { - ExecuterClassName = targetInfo.FileNameWithoutExtension, + ExecuterClassName = targetInfo.EdgeQLFileNameWithoutExtension, EdgeQLHash = targetInfo.EdgeQLHash, ReturnResult = resultType, Parameters = argParameters, @@ -186,7 +283,35 @@ private static GenerationResult GenerateCSharpFromEdgeQL(string @namespace, Gene }; } - private static string BuildTypes(CodecTypeInfo info, out string? resultTypeName, List? usedObjects = null, bool namesOnScalar = false, bool camelCase = false, bool returnTypeName = false) + /// + /// Builds the C# equivalent type from the given . + /// + /// The codec info containing the type information to build. + /// The name of the type or scalar. + /// + /// The reference to a list of used sub-objects, use to keep track of generated + /// reference types. + /// + /// + /// Whether or not to include the on the generated + /// result. + /// + /// + /// Whether or not names will be camel case () or + /// pascal case (). + /// + /// + /// Whether or not to return the type name of objects without generating them. When + /// this is , the object info is added to the + /// . + /// + /// + /// The generated C# of the provided . + /// + /// The is unknown. + private static string BuildTypes(CodecTypeInfo info, out string? resultTypeName, + List? usedObjects = null, bool namesOnScalar = false, bool camelCase = false, + bool returnTypeName = false) { usedObjects ??= new(); var writer = new CodeWriter(); @@ -252,10 +377,20 @@ private static string BuildTypes(CodecTypeInfo info, out string? resultTypeName, throw new InvalidOperationException($"Unknown type def {info}"); } - private static CodecTypeInfo GetTypeOfCodec(ICodec codec, string? name = null, CodecTypeInfo? parent = null) + /// + /// Creates a from the given . + /// + /// The codec to get the type info for. + /// The optional name of the codec. + /// The optional parent of the codec. + /// + /// A representing type information about the provided codec. + /// + /// + /// No could be created from the provided codec. + /// + private static CodecTypeInfo GetTypeInfoFromCodec(ICodec codec, string? name = null, CodecTypeInfo? parent = null) { - // TODO: complete codec parsing - CodecTypeInfo info; switch (codec) @@ -271,7 +406,7 @@ private static CodecTypeInfo GetTypeOfCodec(ICodec codec, string? name = null, C .Select((x, i) => obj.PropertyNames[i] is "__tname__" or "__tid__" ? null - : GetTypeOfCodec(x, obj.PropertyNames[i], info)) + : GetTypeInfoFromCodec(x, obj.PropertyNames[i], info)) .Where(x => x is not null)!; } break; @@ -283,7 +418,7 @@ obj.PropertyNames[i] is "__tname__" or "__tid__" }; info.Children = new[] { - GetTypeOfCodec((ICodec)set.GetType().GetField("_innerCodec", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(set)!, parent: info) + GetTypeInfoFromCodec((ICodec)set.GetType().GetField("_innerCodec", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(set)!, parent: info) }; } break; @@ -295,7 +430,7 @@ obj.PropertyNames[i] is "__tname__" or "__tid__" }; info.Children = new[] { - GetTypeOfCodec((ICodec)array.GetType().GetField("_innerCodec", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(array)!, parent: info) + GetTypeInfoFromCodec((ICodec)array.GetType().GetField("_innerCodec", BindingFlags.Instance | BindingFlags.NonPublic)!.GetValue(array)!, parent: info) }; } break; @@ -305,7 +440,7 @@ obj.PropertyNames[i] is "__tname__" or "__tid__" { IsTuple = true, }; - info.Children = tuple.InnerCodecs.Select(x => GetTypeOfCodec(x, parent: info)); + info.Children = tuple.InnerCodecs.Select(x => GetTypeInfoFromCodec(x, parent: info)); } break; case ICodec scalar when ReflectionUtils.IsSubclassOfInterfaceGeneric(typeof(IScalarCodec<>), codec!.GetType()): @@ -326,17 +461,64 @@ obj.PropertyNames[i] is "__tname__" or "__tid__" return info; } + /// + /// Represents an expanded form of a , containing + /// ease-to-parse information about a codec. + /// private class CodecTypeInfo { - public bool IsArray { get; set; } - public bool IsSet { get; set; } - public bool IsObject { get; set; } - public bool IsTuple { get; set; } + /// + /// Gets whether or not the codec represents an array. + /// + public bool IsArray { get; init; } + + /// + /// Gets whether or not the codec represents a set. + /// + public bool IsSet { get; init; } + + /// + /// Gets whether or not the codec represents an object. + /// + public bool IsObject { get; init; } + + /// + /// Gets whether or not the codec represents a tuple. + /// + public bool IsTuple { get; init; } + + /// + /// Gets or sets the optional name of the codec. + /// public string? Name { get; set; } + + /// + /// Gets or sets the optional dotnet type name that represents what the + /// codec serializes/deserializes. + /// public string? TypeName { get; set; } + + /// + /// Gets or sets the child s for this parent . + /// public IEnumerable? Children { get; set; } + + /// + /// Gets the paret . + /// public CodecTypeInfo? Parent { get; set; } + /// + /// Checks whether or not the current 's body is equal to + /// the given . + /// + /// + /// The to check against. + /// + /// + /// if the 's body matches the + /// current ; otherwise . + /// public bool BodyEquals(CodecTypeInfo info) { return IsArray == info.IsArray && @@ -346,6 +528,12 @@ public bool BodyEquals(CodecTypeInfo info) (info.Children?.SequenceEqual(Children ?? Array.Empty()) ?? false); } + /// + /// Gets a unique name for the current . + /// + /// + /// A unique name representing the current . + /// public string GetUniqueTypeName() { List path = new() { TypeName }; @@ -359,6 +547,7 @@ public string GetUniqueTypeName() return string.Join("", path.Where(x => x is not null)); } + /// public override string ToString() { return $"{Name} ({TypeName})"; diff --git a/src/EdgeDB.Net.CLI/ErrorHandler.cs b/src/EdgeDB.Net.CLI/ErrorHandler.cs deleted file mode 100644 index 15b648a8..00000000 --- a/src/EdgeDB.Net.CLI/ErrorHandler.cs +++ /dev/null @@ -1,36 +0,0 @@ -using CommandLine; -using CommandLine.Text; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace EdgeDB.CLI -{ - internal class ErrorHandler - { - public static void HandleErrors(ParserResult result, IEnumerable errors) - { - foreach(var error in errors) - { - switch (error) - { - case NoVerbSelectedError noVerbs: - { - Console.WriteLine("No command specified use --help to view a list of commands"); - } - break; - default: - HelpText.AutoBuild(result, h => - { - h.Heading = "EdgeDB.Net CLI"; - h.Copyright = "EdgeDB (c) 2022 edgedb.com"; - return h; - }); - break; - } - } - } - } -} diff --git a/src/EdgeDB.Net.CLI/ICommand.cs b/src/EdgeDB.Net.CLI/ICommand.cs index 7c3d5088..3d1986ad 100644 --- a/src/EdgeDB.Net.CLI/ICommand.cs +++ b/src/EdgeDB.Net.CLI/ICommand.cs @@ -2,7 +2,17 @@ namespace EdgeDB.CLI; +/// +/// Represents a generic command that can be executed. +/// interface ICommand { + /// + /// Executes the command, awaiting its completion. + /// + /// The logger for the command. + /// + /// A task that represents the execution flow of the command. + /// Task ExecuteAsync(ILogger logger); } \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Program.cs b/src/EdgeDB.Net.CLI/Program.cs index df3058b0..42c80391 100644 --- a/src/EdgeDB.Net.CLI/Program.cs +++ b/src/EdgeDB.Net.CLI/Program.cs @@ -4,24 +4,30 @@ using EdgeDB.CLI.Arguments; using Serilog; +// intialize our logger Log.Logger = new LoggerConfiguration() .MinimumLevel.Verbose() .WriteTo.Console() .CreateLogger(); +// find all types that extend the 'ICommand' interface. var commands = typeof(Program).Assembly.GetTypes().Where(x => x.GetInterfaces().Any(x => x == typeof(ICommand))); +// create our command line arg parser with no default help writer. var parser = new Parser(x => { x.HelpWriter = null; }); +// parse the 'args'. var result = parser.ParseArguments(args, commands.ToArray()); try { + // execute the parsed result if it is a command. var commandResult = await result.WithParsedAsync(x => { + // if the command supports log args, change the log level for our logger. if(x is LogArgs logArgs) { Log.Logger = new LoggerConfiguration() @@ -30,12 +36,14 @@ .CreateLogger(); } + // execute the command with the logger. return x.ExecuteAsync(Log.Logger); }); - + // if the result was not parsed to a valid command. result.WithNotParsed(err => { + // build the help text. var helpText = HelpText.AutoBuild(commandResult, h => { h.AdditionalNewLineAfterOption = true; @@ -45,14 +53,13 @@ return h; }, e => e, verbsIndex: true); + // write out the help text. Console.WriteLine(helpText); }); } catch (Exception x) { - Console.WriteLine(x); -} - - - + // log the root exception. + Log.Logger.Error(x, "Critical error"); +} \ No newline at end of file diff --git a/src/EdgeDB.Net.CLI/Utils/CodeWriter.cs b/src/EdgeDB.Net.CLI/Utils/CodeWriter.cs index e66a42ef..2bcd71e3 100644 --- a/src/EdgeDB.Net.CLI/Utils/CodeWriter.cs +++ b/src/EdgeDB.Net.CLI/Utils/CodeWriter.cs @@ -4,63 +4,115 @@ namespace EdgeDB.CLI { + /// + /// A utility class for writing code. + /// internal class CodeWriter { + /// + /// The content of the code writer. + /// public readonly StringBuilder Content = new(); + + /// + /// Gets the indentation level of the current code writer. + /// public int IndentLevel { get; private set; } - - private readonly ScopeTracker _scopeTracker; //We only need one. It can be reused. + /// + /// The scope tracker providing an implementation of IDisposable. + /// + private readonly ScopeTracker _scopeTracker; // We only need one. It can be reused. + + /// + /// Creates a new . + /// public CodeWriter() { - _scopeTracker = new(this); //We only need one. It can be reused. + _scopeTracker = new(this); } + /// + /// Appends a string to the current code writer. + /// + /// The line to append public void Append(string line) => Content.Append(line); + /// + /// Appends a line to the current code writer, respecting + /// and ending in a line terminator. + /// + /// The line to append public void AppendLine(string line) => Content.Append(new string(' ', IndentLevel)).AppendLine(line); + /// + /// Appends an empty line to the current code writer, adding a line terminator to the end. + /// public void AppendLine() => Content.AppendLine(); + /// + /// Begins a new scope with the specified line. + /// + /// The line to append. + /// An representing the scope returned. public IDisposable BeginScope(string line) { AppendLine(line); return BeginScope(); } - public IDisposable BeginScope() + /// + /// Begins a new scope, incrementing the indent level until the scope is disposed. + /// + /// An representing the scope returned. + public IDisposable BeginScope() { Content.Append(new string(' ', IndentLevel)).AppendLine("{"); IndentLevel += 4; return _scopeTracker; } - public void EndLine() - => Content.AppendLine(); - + /// + /// Ends a scope, decrementing the indent level. + /// public void EndScope() { IndentLevel -= 4; Content.Append(new string(' ', IndentLevel)).AppendLine("}"); } - public void StartLine() - => Content.Append(new string(' ', IndentLevel)); - + /// + /// Converts the current code writer to a . + /// + /// A string representing the code written to the code writer. public override string ToString() => Content.ToString(); + /// + /// An implementation of responsible for scope decrementing. + /// class ScopeTracker : IDisposable { + /// + /// Gets the That created this . + /// + public CodeWriter Parent { get; } + + /// + /// Constructs a new . + /// + /// The parent that created the . public ScopeTracker(CodeWriter parent) { Parent = parent; } - public CodeWriter Parent { get; } + /// + /// Disposes and ends the scope of this . + /// public void Dispose() { Parent.EndScope(); diff --git a/src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs b/src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs index 18bb2f70..8ed422cf 100644 --- a/src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/ConsoleUtils.cs @@ -6,8 +6,17 @@ namespace EdgeDB.CLI.Utils { + /// + /// A utility class containing methods related to the console. + /// internal class ConsoleUtils { + /// + /// Reads a secret input from STDIN. + /// + /// + /// The entered input received from STDIN. + /// public static string ReadSecretInput() { string input = ""; diff --git a/src/EdgeDB.Net.CLI/Utils/FileUtils.cs b/src/EdgeDB.Net.CLI/Utils/FileUtils.cs index e4cf2693..aef28823 100644 --- a/src/EdgeDB.Net.CLI/Utils/FileUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/FileUtils.cs @@ -6,8 +6,17 @@ namespace EdgeDB.CLI.Utils { - internal class FileUtils + /// + /// A utility class containing methods related to file operations. + /// + internal static class FileUtils { + /// + /// Waits synchronously for a file to be released. + /// + /// The file path. + /// The timeout. + /// if the file was released; otherwise . public static bool WaitForHotFile(string path, int timeout = 5000) { var start = DateTime.UtcNow; diff --git a/src/EdgeDB.Net.CLI/Utils/HashUtils.cs b/src/EdgeDB.Net.CLI/Utils/HashUtils.cs index 1d6a65df..60716046 100644 --- a/src/EdgeDB.Net.CLI/Utils/HashUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/HashUtils.cs @@ -8,8 +8,16 @@ namespace EdgeDB.CLI.Utils { - internal class HashUtils + /// + /// A utility class containing methods related to hashes. + /// + internal static class HashUtils { + /// + /// Hashes edgeql for an autogenerated file header. + /// + /// The edgeql to hash. + /// The hashed edgeql as hex. public static string HashEdgeQL(string edgeql) { using var algo = SHA256.Create(); diff --git a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs index da1abfa9..750e7bd0 100644 --- a/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/ProjectUtils.cs @@ -8,9 +8,17 @@ using System.Threading.Tasks; namespace EdgeDB.CLI.Utils -{ - internal class ProjectUtils +{ + /// + /// A utility class containing methods realted to edgedb projects. + /// + internal static class ProjectUtils { + /// + /// Gets the edgedb project root from the current directory. + /// + /// The project root directory. + /// The project could not be found. public static string GetProjectRoot() { var directory = Environment.CurrentDirectory; @@ -27,6 +35,14 @@ public static string GetProjectRoot() return directory; } + /// + /// Starts a watcher process. + /// + /// The connection info for the watcher process. + /// The project root directory. + /// The output directory for files the watcher generates to place. + /// The namespace for generated files. + /// The started watcher process id. public static int StartWatchProcess(EdgeDBConnection connection, string root, string outputDir, string @namespace) { var current = Process.GetCurrentProcess(); @@ -42,6 +58,13 @@ public static int StartWatchProcess(EdgeDBConnection connection, string root, st })!.Id; } + /// + /// Gets the watcher process for the provided root directory. + /// + /// The project root. + /// + /// The watcher or if not found. + /// public static Process? GetWatcherProcess(string root) { var file = Path.Combine(root, "edgeql.dotnet.watcher.process"); @@ -57,6 +80,10 @@ public static int StartWatchProcess(EdgeDBConnection connection, string root, st return null; } + /// + /// Registers the current process as the watcher project for the given project root. + /// + /// The project root. public static void RegisterProcessAsWatcher(string root) { var id = Environment.ProcessId; @@ -77,6 +104,12 @@ public static void RegisterProcessAsWatcher(string root) } } + /// + /// Creates a dotnet project. + /// + /// The target directory. + /// The name of the project + /// The project failed to be created. public static async Task CreateGeneratedProjectAsync(string root, string name) { var result = await Cli.Wrap("dotnet") @@ -103,6 +136,16 @@ public static async Task CreateGeneratedProjectAsync(string root, string name) File.Delete(Path.Combine(root, name, "Class1.cs")); } + /// + /// Gets a list of edgeql file paths for the provided root directory. + /// + /// + /// migration files are ignored. + /// + /// The root directory to scan for edgeql files. + /// + /// An that enumerates a collection of files ending in .edgeql. + /// public static IEnumerable GetTargetEdgeQLFiles(string root) => Directory.GetFiles(root, "*.edgeql", SearchOption.AllDirectories).Where(x => !x.StartsWith(Path.Combine(root, "dbschema", "migrations"))); } diff --git a/src/EdgeDB.Net.CLI/Utils/TextUtils.cs b/src/EdgeDB.Net.CLI/Utils/TextUtils.cs index c5feec6b..61e2eb69 100644 --- a/src/EdgeDB.Net.CLI/Utils/TextUtils.cs +++ b/src/EdgeDB.Net.CLI/Utils/TextUtils.cs @@ -8,10 +8,21 @@ namespace EdgeDB.CLI.Utils { - internal class TextUtils + /// + /// A utility class containing methods related to text operations. + /// + internal static class TextUtils { + /// + /// The current culture info. + /// private static CultureInfo? _cultureInfo; + /// + /// Converts the given string to pascal case. + /// + /// The string to convert to pascal case. + /// A pascal-cased version of the input string. public static string ToPascalCase(string input) { _cultureInfo ??= CultureInfo.CurrentCulture; @@ -20,6 +31,11 @@ public static string ToPascalCase(string input) return _cultureInfo.TextInfo.ToTitleCase(t.Replace("_", " ")).Replace(" ", ""); } + /// + /// Converts the given string to camel case. + /// + /// The string to convert to pascal case. + /// A camel-cased version of the input string. public static string ToCamelCase(string input) { var p = ToPascalCase(input); From 68f82a0ed9cd8b12f23444e621b0d5530d365a30 Mon Sep 17 00:00:00 2001 From: quinchs <49576606+quinchs@users.noreply.github.com> Date: Tue, 30 Aug 2022 16:22:13 -0700 Subject: [PATCH 13/13] add check for conflicting file names --- src/EdgeDB.Net.CLI/Commands/Generate.cs | 12 ++++++++++++ src/EdgeDB.Net.CLI/Program.cs | 2 +- 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/src/EdgeDB.Net.CLI/Commands/Generate.cs b/src/EdgeDB.Net.CLI/Commands/Generate.cs index fdf608f0..d795437c 100644 --- a/src/EdgeDB.Net.CLI/Commands/Generate.cs +++ b/src/EdgeDB.Net.CLI/Commands/Generate.cs @@ -75,6 +75,18 @@ public async Task ExecuteAsync(ILogger logger) // find edgeql files var edgeqlFiles = ProjectUtils.GetTargetEdgeQLFiles(projectRoot).ToArray(); + + // error if any are the same name + var groupFileNames = edgeqlFiles.GroupBy(x => Path.GetFileNameWithoutExtension(x)); + if(groupFileNames.Any(x => x.Count() > 1)) + { + foreach(var conflict in groupFileNames.Where(x => x.Count() > 1)) + { + logger.Fatal($"{{@Count}} files contain the same name ({string.Join(" - ", conflict.Select(x => x))})", conflict.Count()); + } + + return; + } logger.Information("Generating {@FileCount} files...", edgeqlFiles.Length); diff --git a/src/EdgeDB.Net.CLI/Program.cs b/src/EdgeDB.Net.CLI/Program.cs index 42c80391..83d5ffa1 100644 --- a/src/EdgeDB.Net.CLI/Program.cs +++ b/src/EdgeDB.Net.CLI/Program.cs @@ -61,5 +61,5 @@ catch (Exception x) { // log the root exception. - Log.Logger.Error(x, "Critical error"); + Log.Logger.Fatal(x, "Critical error"); } \ No newline at end of file