diff --git a/ConductorSharp.sln b/ConductorSharp.sln index f0f8822f..94732eff 100644 --- a/ConductorSharp.sln +++ b/ConductorSharp.sln @@ -1,73 +1,79 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31919.166 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Engine", "src\ConductorSharp.Engine\ConductorSharp.Engine.csproj", "{22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Client", "src\ConductorSharp.Client\ConductorSharp.Client.csproj", "{3244BEE8-17C4-4B00-BEB1-A41CC3E95482}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.ApiEnabled", "examples\ConductorSharp.ApiEnabled\ConductorSharp.ApiEnabled.csproj", "{C350E5E2-EA36-4816-BCBB-CBB597AAFF78}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Definitions", "examples\ConductorSharp.Definitions\ConductorSharp.Definitions.csproj", "{68400F84-CE5A-4FFA-A417-05C798ECD5A4}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{16F68738-1ED3-4646-BE01-DB9246C10E89}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.NoApi", "examples\ConductorSharp.NoApi\ConductorSharp.NoApi.csproj", "{44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}" -EndProject -Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{C398B7D9-C238-4169-86A8-B03D01FBB2BE}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ConductorSharp.Engine.Tests", "test\ConductorSharp.Engine.Tests\ConductorSharp.Engine.Tests.csproj", "{45AB7886-41DB-4A0E-9667-1C78D63F0FC7}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DE1FA421-B8D6-45A7-9667-0798A241A89C}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - EndProjectSection -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Debug|Any CPU.Build.0 = Debug|Any CPU - {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Release|Any CPU.ActiveCfg = Release|Any CPU - {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Release|Any CPU.Build.0 = Release|Any CPU - {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Debug|Any CPU.Build.0 = Debug|Any CPU - {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Release|Any CPU.ActiveCfg = Release|Any CPU - {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Release|Any CPU.Build.0 = Release|Any CPU - {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Release|Any CPU.Build.0 = Release|Any CPU - {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Debug|Any CPU.Build.0 = Debug|Any CPU - {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Release|Any CPU.ActiveCfg = Release|Any CPU - {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Release|Any CPU.Build.0 = Release|Any CPU - {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU - {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU - {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Release|Any CPU.Build.0 = Release|Any CPU - {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Release|Any CPU.Build.0 = Release|Any CPU - {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(NestedProjects) = preSolution - {C350E5E2-EA36-4816-BCBB-CBB597AAFF78} = {16F68738-1ED3-4646-BE01-DB9246C10E89} - {68400F84-CE5A-4FFA-A417-05C798ECD5A4} = {16F68738-1ED3-4646-BE01-DB9246C10E89} - {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9} = {16F68738-1ED3-4646-BE01-DB9246C10E89} - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {2443B3EC-E590-4C83-8BE2-A0787C66307C} - EndGlobalSection -EndGlobal + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31919.166 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Engine", "src\ConductorSharp.Engine\ConductorSharp.Engine.csproj", "{22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Client", "src\ConductorSharp.Client\ConductorSharp.Client.csproj", "{3244BEE8-17C4-4B00-BEB1-A41CC3E95482}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.ApiEnabled", "examples\ConductorSharp.ApiEnabled\ConductorSharp.ApiEnabled.csproj", "{C350E5E2-EA36-4816-BCBB-CBB597AAFF78}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Definitions", "examples\ConductorSharp.Definitions\ConductorSharp.Definitions.csproj", "{68400F84-CE5A-4FFA-A417-05C798ECD5A4}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Examples", "Examples", "{16F68738-1ED3-4646-BE01-DB9246C10E89}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.NoApi", "examples\ConductorSharp.NoApi\ConductorSharp.NoApi.csproj", "{44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}" +EndProject +Project("{E53339B2-1760-4266-BCC7-CA923CBCF16C}") = "docker-compose", "docker-compose.dcproj", "{C398B7D9-C238-4169-86A8-B03D01FBB2BE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Engine.Tests", "test\ConductorSharp.Engine.Tests\ConductorSharp.Engine.Tests.csproj", "{45AB7886-41DB-4A0E-9667-1C78D63F0FC7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ConductorSharp.Toolkit", "src\ConductorSharp.Toolkit\ConductorSharp.Toolkit.csproj", "{7FC97985-07CE-4643-8415-C48BA2117D88}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{DE1FA421-B8D6-45A7-9667-0798A241A89C}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22BDC06E-50E5-4FE4-97D2-CD9E07EF4842}.Release|Any CPU.Build.0 = Release|Any CPU + {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3244BEE8-17C4-4B00-BEB1-A41CC3E95482}.Release|Any CPU.Build.0 = Release|Any CPU + {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C350E5E2-EA36-4816-BCBB-CBB597AAFF78}.Release|Any CPU.Build.0 = Release|Any CPU + {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {68400F84-CE5A-4FFA-A417-05C798ECD5A4}.Release|Any CPU.Build.0 = Release|Any CPU + {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9}.Release|Any CPU.Build.0 = Release|Any CPU + {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C398B7D9-C238-4169-86A8-B03D01FBB2BE}.Release|Any CPU.Build.0 = Release|Any CPU + {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {45AB7886-41DB-4A0E-9667-1C78D63F0FC7}.Release|Any CPU.Build.0 = Release|Any CPU + {7FC97985-07CE-4643-8415-C48BA2117D88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7FC97985-07CE-4643-8415-C48BA2117D88}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7FC97985-07CE-4643-8415-C48BA2117D88}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7FC97985-07CE-4643-8415-C48BA2117D88}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {C350E5E2-EA36-4816-BCBB-CBB597AAFF78} = {16F68738-1ED3-4646-BE01-DB9246C10E89} + {68400F84-CE5A-4FFA-A417-05C798ECD5A4} = {16F68738-1ED3-4646-BE01-DB9246C10E89} + {44E887F8-5E0D-4729-A4A8-AFEBBF87C6F9} = {16F68738-1ED3-4646-BE01-DB9246C10E89} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {2443B3EC-E590-4C83-8BE2-A0787C66307C} + EndGlobalSection +EndGlobal diff --git a/src/ConductorSharp.Toolkit/Commands/Command.cs b/src/ConductorSharp.Toolkit/Commands/Command.cs new file mode 100644 index 00000000..165f6e79 --- /dev/null +++ b/src/ConductorSharp.Toolkit/Commands/Command.cs @@ -0,0 +1,10 @@ +using ConductorSharp.Toolkit.Models; + +namespace ConductorSharp.Toolkit.Commands +{ + public interface Command + { + public string GetName(); + public Task Execute(CommandInput input); + } +} diff --git a/src/ConductorSharp.Toolkit/Commands/CommandRegistry.cs b/src/ConductorSharp.Toolkit/Commands/CommandRegistry.cs new file mode 100644 index 00000000..3e2cdd7c --- /dev/null +++ b/src/ConductorSharp.Toolkit/Commands/CommandRegistry.cs @@ -0,0 +1,14 @@ +namespace ConductorSharp.Toolkit.Commands +{ + public class CommandRegistry + { + private readonly IEnumerable _commands; + + public CommandRegistry(IEnumerable commands) + { + _commands = commands; + } + + public Command Get(string name) => _commands.FirstOrDefault(a => a.GetName() == name); + } +} diff --git a/src/ConductorSharp.Toolkit/Commands/ScaffoldCommand.cs b/src/ConductorSharp.Toolkit/Commands/ScaffoldCommand.cs new file mode 100644 index 00000000..9f57c8a0 --- /dev/null +++ b/src/ConductorSharp.Toolkit/Commands/ScaffoldCommand.cs @@ -0,0 +1,20 @@ +using ConductorSharp.Toolkit.Models; +using ConductorSharp.Toolkit.Service; + +namespace ConductorSharp.Toolkit.Commands +{ + public class ScaffoldCommand : Command + { + private readonly IScaffoldingService _scaffoldingService; + public ScaffoldCommand(IScaffoldingService scaffoldingService) + { + _scaffoldingService = scaffoldingService; + } + + public string GetName() => "scaffold"; + public async Task Execute(CommandInput input) + { + await _scaffoldingService.Scaffold(); + } + } +} diff --git a/src/ConductorSharp.Toolkit/ConductorSharp.Toolkit.csproj b/src/ConductorSharp.Toolkit/ConductorSharp.Toolkit.csproj new file mode 100644 index 00000000..bdc8bcc6 --- /dev/null +++ b/src/ConductorSharp.Toolkit/ConductorSharp.Toolkit.csproj @@ -0,0 +1,36 @@ + + + + Exe + net6.0 + enable + disable + true + dotnet-conductorsharp + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/ConductorSharp.Toolkit/Models/CommandInput.cs b/src/ConductorSharp.Toolkit/Models/CommandInput.cs new file mode 100644 index 00000000..19b2d75d --- /dev/null +++ b/src/ConductorSharp.Toolkit/Models/CommandInput.cs @@ -0,0 +1,11 @@ +namespace ConductorSharp.Toolkit.Models +{ + public class CommandInput + { + public string Api { get; set; } + public string Namespace { get; set; } + public string Host { get; set; } + public bool Dryrun { get; set; } + public string Destination { get; set; } + } +} diff --git a/src/ConductorSharp.Toolkit/Models/WorkerDocument.cs b/src/ConductorSharp.Toolkit/Models/WorkerDocument.cs new file mode 100644 index 00000000..53cc626f --- /dev/null +++ b/src/ConductorSharp.Toolkit/Models/WorkerDocument.cs @@ -0,0 +1,25 @@ +using System.Collections; + +namespace ConductorSharp.Toolkit.Models +{ + public class WorkerDocument + { + public WorkerDocument() + { + InputTable = new List(); + OutputTable = new List(); + UpdatedWorkers = new Hashtable(); + } + + public string SourcePath { get; set; } + public string DestinationPath { get; set; } + public string WorkerName { get; set; } + public string WorkerDescription { get; set; } //short description of worker + public List InputTable { get; set; } + public List OutputTable { get; set; } + public Hashtable UpdatedWorkers { get; set; } //to prevent us from adding the same task twice or more, because it can happen that several workflows use the same worker + public string OwnerApp { get; set; } + public string OwnerEmail { get; set; } + public string RegistrationTime { get; set; } + } +} diff --git a/src/ConductorSharp.Toolkit/Models/WorkflowDocument.cs b/src/ConductorSharp.Toolkit/Models/WorkflowDocument.cs new file mode 100644 index 00000000..39bf7b9a --- /dev/null +++ b/src/ConductorSharp.Toolkit/Models/WorkflowDocument.cs @@ -0,0 +1,19 @@ +namespace ConductorSharp.Toolkit.Models +{ + public class WorkflowDocument + { + public string SourcePath { get; set; } //path to .md file of the workflow + public string DestinationPath { get; set; } + public string WorkflowName { get; set; } + public string WorkflowDescription { get; set; } //short description of workflow + public List DiagramCode { get; set; } + public List InputTable { get; set; } + public List TaskTable { get; set; } + public List OutputTable { get; set; } + public string OwnerApp { get; set; } + public string OwnerEmail { get; set; } + public string Version { get; set; } + public string FailureWorkflow { get; set; } + public string RegistrationTime { get; set; } + } +} diff --git a/src/ConductorSharp.Toolkit/Program.cs b/src/ConductorSharp.Toolkit/Program.cs new file mode 100644 index 00000000..38bae6f9 --- /dev/null +++ b/src/ConductorSharp.Toolkit/Program.cs @@ -0,0 +1,77 @@ +using Autofac; +using Autofac.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection; +using ConductorSharp.Engine.Extensions; +using ConductorSharp.Toolkit.Commands; +using ConductorSharp.Toolkit.Models; + +namespace ConductorSharp.Toolkit +{ + class Program + { + public async static Task Main(string[] args) + { + try + { + var action = args[0]; + + var input = ParseInput(args); + + var container = BuildContainer(input); + + var commandRegistry = container.Resolve(); + + var command = commandRegistry.Get(action); + + await command.Execute(input); + } + catch (Exception exc) + { + PrintHelp(); + } + } + + private static void PrintHelp() => Console.WriteLine("PLACEHOLDER HELP"); + + private static IContainer BuildContainer(CommandInput input) + { + var serviceCollection = new ServiceCollection(); + + serviceCollection.AddLogging(); + + var builder = new ContainerBuilder(); + + serviceCollection.Configure( + config => + { + config.ApiUrl = input.Api; + config.BaseUrl = input.Host; + config.BaseNamespace = input.Namespace; + config.Dryrun = input.Dryrun; + config.Destination = input.Destination; + } + ); + + builder.Populate(serviceCollection); + builder.AddWorkflowEngine(input.Host, input.Api); + builder.RegisterModule(new ToolkitModule()); + + return builder.Build(); + } + + private static CommandInput ParseInput(string[] args) + { + var action = args[0]; + var inputParameters = args.Skip(1).Select(a => new KeyValuePair(a.Split("=")[0], a.Split("=")[1])).ToList(); + + return new CommandInput + { + Api = inputParameters.Where(a => a.Key == "path").Select(a => a.Value).FirstOrDefault(), + Namespace = inputParameters.Where(a => a.Key == "namespace").Select(a => a.Value).FirstOrDefault(), + Host = inputParameters.Where(a => a.Key == "host").Select(a => a.Value).FirstOrDefault(), + Dryrun = inputParameters.Where(a => a.Key == "dryrun").Select(a => bool.Parse(a.Value)).FirstOrDefault(), + Destination = inputParameters.Where(a => a.Key == "destination").Select(a => a.Value).FirstOrDefault() + }; + } + } +} diff --git a/src/ConductorSharp.Toolkit/ScaffoldingConfig.cs b/src/ConductorSharp.Toolkit/ScaffoldingConfig.cs new file mode 100644 index 00000000..3cc20542 --- /dev/null +++ b/src/ConductorSharp.Toolkit/ScaffoldingConfig.cs @@ -0,0 +1,11 @@ +namespace ConductorSharp.Toolkit +{ + public class ScaffoldingConfig + { + public string BaseNamespace { get; set; } + public string ApiUrl { get; set; } + public string BaseUrl { get; set; } + public bool Dryrun { get; set; } + public string Destination { get; set; } + } +} diff --git a/src/ConductorSharp.Toolkit/Service/IScaffoldingService.cs b/src/ConductorSharp.Toolkit/Service/IScaffoldingService.cs new file mode 100644 index 00000000..5dca7b3a --- /dev/null +++ b/src/ConductorSharp.Toolkit/Service/IScaffoldingService.cs @@ -0,0 +1,7 @@ +namespace ConductorSharp.Toolkit.Service +{ + public interface IScaffoldingService + { + Task Scaffold(); + } +} diff --git a/src/ConductorSharp.Toolkit/Service/ScaffoldingService.cs b/src/ConductorSharp.Toolkit/Service/ScaffoldingService.cs new file mode 100644 index 00000000..9460bd8f --- /dev/null +++ b/src/ConductorSharp.Toolkit/Service/ScaffoldingService.cs @@ -0,0 +1,271 @@ +using ConductorSharp.Client.Model.Common; +using ConductorSharp.Client.Service; +using ConductorSharp.Engine.Util; +using ConductorSharp.Toolkit.Util; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; +using Newtonsoft.Json.Linq; +using System.Text; +using System.Text.RegularExpressions; + +namespace ConductorSharp.Toolkit.Service +{ + public class ScaffoldingService : IScaffoldingService + { + private readonly IMetadataService _metadataService; + private readonly ILogger _logger; + private readonly ScaffoldingConfig _config; + + public ScaffoldingService(IMetadataService metadataService, IOptions options, ILogger logger) + { + _metadataService = metadataService; + _logger = logger; + _config = options.Value; + } + + public async Task Scaffold() + { + var workflowDefinitions = await _metadataService.GetAllWorkflowDefinitions(); + var workflowCollectionBuilder = new StringBuilder(); + foreach (var workflowDefinition in workflowDefinitions) + { + var workflowClass = CreateWorkflowClass(workflowDefinition); + + if (workflowClass != null) + workflowCollectionBuilder.Append(workflowClass); + } + + var taskDefinitions = await _metadataService.GetAllTaskDefinitions(); + + var taskCollectionBuilder = new StringBuilder(); + foreach (var taskDefinition in taskDefinitions) + { + var taskClass = CreateTaskClass(taskDefinition); + + if (taskClass != null) + taskCollectionBuilder.Append(taskClass); + } + + var taskTemplate = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Templates/TaskCollectionTemplate.default"); + var taskCollection = taskCollectionBuilder.ToString(); + taskTemplate = taskTemplate.Replace("{{namespace}}", _config.BaseNamespace + ".Tasks"); + taskTemplate = taskTemplate.Replace("{{taskCollection}}", taskCollection); + var dir = _config.Destination + Path.DirectorySeparatorChar + "Tasks"; + + Directory.CreateDirectory(dir); + + File.WriteAllText(dir + Path.DirectorySeparatorChar + "Tasks.scaff.cs", taskTemplate); + + var workflowTemplate = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Templates/WorkflowCollectionTemplate.default"); + var workflowCollection = workflowCollectionBuilder.ToString(); + workflowTemplate = workflowTemplate.Replace("{{namespace}}", _config.BaseNamespace + ".Workflows"); + workflowTemplate = workflowTemplate.Replace("{{workflowCollection}}", workflowCollection); + var workflowDir = _config.Destination + Path.DirectorySeparatorChar + "Workflows"; + + Directory.CreateDirectory(workflowDir); + + File.WriteAllText(workflowDir + Path.DirectorySeparatorChar + "Workflows.scaff.cs", workflowTemplate); + } + + public string CreateWorkflowClass(WorkflowDefinition workflowDefinition) + { + var lines = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Templates/WorkflowTemplate.default"); + + string name = SnakeCaseUtil.ToPascalCase($"{workflowDefinition.Name}_V{workflowDefinition.Version}").Trim(); + string note = null; + if (LangUtils.MakeValidMemberName(name, "A", out name)) + { + note = "The autogenerated name has been changed because it is invalid C# member name"; + } + + string workflowDescription = workflowDefinition.Description; + string[] labels; + + try + { + var descriptionObject = JObject.Parse(workflowDescription); + workflowDescription = descriptionObject.SelectToken("description")?.Value(); + var labelsObject = descriptionObject.SelectToken("labels"); + + if (labelsObject is JArray labelsArray) + { + labels = labelsArray.ToObject(); + } + } + catch (Exception) + { + _logger.LogWarning("Unable to parse description for workflow {0}", workflowDefinition.Name); + } + + var inputLinesBuilder = new StringBuilder(); + try + { + var inputParameters = JObject.Parse(workflowDefinition.InputParametersJSON[0]); + + foreach (var param in inputParameters) + { + var value = param.Value.SelectToken("value")?.ToString(Newtonsoft.Json.Formatting.None); + var type = param.Value.SelectToken("type")?.Value(); + var description = param.Value.SelectToken("description")?.Value(); + + if (type == null) + type = "string"; + + AppendXmlComment(inputLinesBuilder, "originalName", param.Key); + + if (!string.IsNullOrEmpty(description)) + AppendXmlComment(inputLinesBuilder, "summary", value); + + switch (type) + { + case "string": + case "integer": + if (!string.IsNullOrEmpty(value)) + { + AppendXmlComment(inputLinesBuilder, "remark", $"Example: {value}"); + } + AppendClassProperty(inputLinesBuilder, "dynamic", param.Key); + break; + case "toggle": + AppendClassProperty(inputLinesBuilder, "dynamic", param.Key); + break; + case "select": + var optionsToken = param.Value.SelectToken("options"); + + string[] options = null; + + if (optionsToken is JArray optionsArray) + { + options = optionsArray.ToObject(); + } + if (options != null && options.Length > 0) + AppendXmlComment(inputLinesBuilder, "remark", $"Options: {string.Join(',', options)}"); + + AppendClassProperty(inputLinesBuilder, "dynamic", param.Key); + break; + default: + break; + } + } + } + catch (Exception) + { + if (workflowDefinition.InputParametersJSON != null) + Console.WriteLine( + $"Unable to parse input parameters {string.Join(',', workflowDefinition.InputParametersJSON)} for {workflowDefinition.Name})" + ); + else + Console.WriteLine($"No input parameters defined for {workflowDefinition.Name}"); + } + + if (string.IsNullOrEmpty(workflowDefinition.OwnerEmail)) + _logger.LogWarning($"No owner email defined for task {workflowDefinition.Name}"); + + if (string.IsNullOrEmpty(workflowDefinition.OwnerApp)) + _logger.LogWarning($"No owner app defined for task {workflowDefinition.Name}"); + + lines = lines.Replace("{{inputProperties}}", inputLinesBuilder.ToString()); + lines = lines.Replace("{{workflowName}}", name); + lines = lines.Replace("{{note}}", note); + lines = lines.Replace("{{ownerEmail}}", $"{workflowDefinition.OwnerEmail}"); + lines = lines.Replace("{{ownerApp}}", $"{workflowDefinition.OwnerApp}"); + lines = lines.Replace("{{originalName}}", $"{workflowDefinition.Name}"); + lines = lines.Replace("{{description}}", workflowDescription); + lines = lines.Replace("{{commentDescription}}", workflowDescription?.Replace('\n', ',')); + + if (_config.Dryrun) + return null; + + return lines; + } + + private void AppendClassProperty(StringBuilder linesBuilder, string type, string name) + { + linesBuilder.AppendLine($" [JsonProperty(\"{name}\")]"); + LangUtils.MakeValidMemberName(SnakeCaseUtil.ToPascalCase(name), "A", out var propertyName); + linesBuilder.AppendLine($" public {type} {propertyName} {{ get; set; }}"); + } + + private void AppendXmlComment(StringBuilder linesBuilder, string tag, string value) + { + linesBuilder.AppendLine($" /// <{tag}>"); + linesBuilder.AppendLine($" /// {value}"); + linesBuilder.AppendLine($" /// "); + } + + public string CreateTaskClass(TaskDefinition taskDefinition) + { + var lines = EmbeddedFileHelper.GetLinesFromEmbeddedFile("~/Templates/WorkerTemplate.default"); + + var namespaceRegex = new Regex("(^[^_]+)_.+"); + + var matches = namespaceRegex.Match(taskDefinition.Name).Groups; + + string naspace = ""; + if (matches.Count > 1) + { + naspace = matches[1].Value.ToLowerInvariant(); + naspace = char.ToUpperInvariant(naspace[0]) + naspace[1..]; + } + + string name = SnakeCaseUtil.ToPascalCase(taskDefinition.Name).Trim(); + string note = null; + if (LangUtils.MakeValidMemberName(name, "A", out name)) + { + note = "The autogenerated name has been prepended because it starts with a digit character."; + } + + var inputLinesBuilder = new StringBuilder(); + foreach (var property in taskDefinition.InputKeys) + { + AppendXmlComment(inputLinesBuilder, "originalName", property); + AppendClassProperty(inputLinesBuilder, "dynamic", property); + } + + var outputLinesBuilder = new StringBuilder(); + foreach (var property in taskDefinition.OutputKeys) + { + AppendXmlComment(outputLinesBuilder, "originalName", property); + AppendClassProperty(outputLinesBuilder, "dynamic", property); + } + + var generatedNamespace = _config.BaseNamespace + ".Tasks" + (!string.IsNullOrEmpty(naspace) ? $".{naspace}" : ""); + + lines = lines.Replace("{{workerName}}", name); + lines = lines.Replace("{{note}}", note); + lines = lines.Replace("{{inputProperties}}", inputLinesBuilder.ToString()); + lines = lines.Replace("{{outputProperties}}", outputLinesBuilder.ToString()); + lines = lines.Replace("{{namespace}}", $"{generatedNamespace}"); + lines = lines.Replace("{{ownerEmail}}", $"{taskDefinition.OwnerEmail}"); + lines = lines.Replace("{{ownerApp}}", $"{taskDefinition.OwnerApp}"); + lines = lines.Replace("{{originalName}}", $"{taskDefinition.Name}"); + + var description = ""; + try + { + description = JObject.Parse(taskDefinition.Description).SelectToken("description").Value(); + } + catch (Exception) + { + _logger.LogWarning($"Invalid description '{taskDefinition.Description}' in task {taskDefinition.Name}"); + } + + var ownerEmail = taskDefinition.OwnerEmail; + var ownerApp = taskDefinition.OwnerApp; + + if (string.IsNullOrEmpty(ownerEmail)) + _logger.LogWarning($"No owner email defined for task {taskDefinition.Name}"); + + if (string.IsNullOrEmpty(ownerApp)) + _logger.LogWarning($"No owner app defined for task {taskDefinition.Name}"); + + lines = lines.Replace("{{description}}", description); + lines = lines.Replace("{{commentDescription}}", description.Replace('\n', ',')); + + if (_config.Dryrun) + return null; + + return lines; + } + } +} diff --git a/src/ConductorSharp.Toolkit/Templates/TaskCollectionTemplate.default b/src/ConductorSharp.Toolkit/Templates/TaskCollectionTemplate.default new file mode 100644 index 00000000..38ae9b09 --- /dev/null +++ b/src/ConductorSharp.Toolkit/Templates/TaskCollectionTemplate.default @@ -0,0 +1,20 @@ +using ConductorSharp.Engine.Model; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using MediatR; +using ConductorSharp.Engine.Util; + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace {{namespace}} +{ + {{taskCollection}} +} diff --git a/src/ConductorSharp.Toolkit/Templates/WorkerTemplate.default b/src/ConductorSharp.Toolkit/Templates/WorkerTemplate.default new file mode 100644 index 00000000..bd834386 --- /dev/null +++ b/src/ConductorSharp.Toolkit/Templates/WorkerTemplate.default @@ -0,0 +1,31 @@ + public partial class {{workerName}}Input : IRequest<{{workerName}}Output> + { +{{inputProperties}} + } + + public partial class {{workerName}}Output + { +{{outputProperties}} + } + + /// + /// {{commentDescription}} + /// + /// + /// {{originalName}} + /// + /// + /// {{ownerEmail}} + /// + /// + /// {{ownerApp}} + /// + /// + /// {{note}} + /// + [OriginalName("{{originalName}}")] + public partial class {{workerName}} : SimpleTaskModel<{{workerName}}Input, {{workerName}}Output> + { + } + + diff --git a/src/ConductorSharp.Toolkit/Templates/WorkflowCollectionTemplate.default b/src/ConductorSharp.Toolkit/Templates/WorkflowCollectionTemplate.default new file mode 100644 index 00000000..98daf1a7 --- /dev/null +++ b/src/ConductorSharp.Toolkit/Templates/WorkflowCollectionTemplate.default @@ -0,0 +1,21 @@ +using ConductorSharp.Engine.Model; +using System.ComponentModel; +using System.ComponentModel.DataAnnotations; +using Newtonsoft.Json; +using ConductorSharp.Engine.Builders; +using MediatR; +using ConductorSharp.Engine.Util; + +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace {{namespace}} +{ + {{workflowCollection}} +} diff --git a/src/ConductorSharp.Toolkit/Templates/WorkflowTemplate.default b/src/ConductorSharp.Toolkit/Templates/WorkflowTemplate.default new file mode 100644 index 00000000..684cccd7 --- /dev/null +++ b/src/ConductorSharp.Toolkit/Templates/WorkflowTemplate.default @@ -0,0 +1,30 @@ + public partial class {{workflowName}}Input : IRequest<{{workflowName}}Output> + { +{{inputProperties}} + } + + public partial class {{workflowName}}Output + { + } + + /// + /// {{commentDescription}} + /// + /// + /// {{originalName}} + /// + /// + /// {{ownerEmail}} + /// + /// + /// {{ownerApp}} + /// + /// + /// {{note}} + /// + [OriginalName("{{originalName}}")] + public partial class {{workflowName}} : SubWorkflowTaskModel<{{workflowName}}Input, {{workflowName}}Output> + { + } + + diff --git a/src/ConductorSharp.Toolkit/ToolkitModule.cs b/src/ConductorSharp.Toolkit/ToolkitModule.cs new file mode 100644 index 00000000..60e67104 --- /dev/null +++ b/src/ConductorSharp.Toolkit/ToolkitModule.cs @@ -0,0 +1,19 @@ +using Autofac; +using ConductorSharp.Client.Service; +using ConductorSharp.Toolkit.Commands; +using ConductorSharp.Toolkit.Service; + +namespace ConductorSharp.Toolkit +{ + public class ToolkitModule : Module + { + protected override void Load(ContainerBuilder builder) + { + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType().As(); + builder.RegisterType(); + builder.RegisterType().As(); + } + } +} diff --git a/src/ConductorSharp.Toolkit/Util/EmbeddedFileHelper.cs b/src/ConductorSharp.Toolkit/Util/EmbeddedFileHelper.cs new file mode 100644 index 00000000..47b3f9a9 --- /dev/null +++ b/src/ConductorSharp.Toolkit/Util/EmbeddedFileHelper.cs @@ -0,0 +1,52 @@ +using Newtonsoft.Json; +using System.Reflection; +using System.Text; + +namespace ConductorSharp.Toolkit.Util +{ + internal static class EmbeddedFileHelper + { + private static string ReadAssemblyFile(Assembly assembly, string name) + { + var stream = assembly.GetManifestResourceStream(name); + + if (stream == null) + throw new InvalidOperationException($"Resource {name} does not exist."); + + using var reader = new StreamReader(stream, Encoding.UTF8); + + return reader.ReadToEnd(); + } + + public static T GetObjectFromEmbeddedFile(string fileName, params (string Key, object Value)[] templateParams) + { + fileName = fileName.Replace("~/", typeof(EmbeddedFileHelper).Assembly.GetName().Name + ".").Replace("/", "."); + + var contents = ReadAssemblyFile(typeof(EmbeddedFileHelper).Assembly, fileName); + + if (templateParams != null) + foreach (var (Key, Value) in templateParams) + contents = contents.Replace("{{" + Key + "}}", $"{Value}"); + + return JsonConvert.DeserializeObject(contents); + } + + public static string GetLinesFromEmbeddedFile(string fileName) + { + fileName = fileName.Replace("~/", typeof(EmbeddedFileHelper).Assembly.GetName().Name + ".").Replace("/", "."); + + var contents = ReadAssemblyFile(typeof(EmbeddedFileHelper).Assembly, fileName); + + return contents; + } + + public static Task GetObjectFromEmbeddedFileAsync(string fileName, params (string Key, object Value)[] templateParams) => + Task.FromResult(GetObjectFromEmbeddedFile(fileName, templateParams)); + + public static string Reserialize(string fileName, params (string Key, object Value)[] templateParams) + { + var file = GetObjectFromEmbeddedFile(fileName, templateParams); + return JsonConvert.SerializeObject(file); + } + } +} diff --git a/src/ConductorSharp.Toolkit/Util/LangUtils.cs b/src/ConductorSharp.Toolkit/Util/LangUtils.cs new file mode 100644 index 00000000..2807c5ec --- /dev/null +++ b/src/ConductorSharp.Toolkit/Util/LangUtils.cs @@ -0,0 +1,34 @@ +using System.Text.RegularExpressions; + +namespace ConductorSharp.Toolkit.Util +{ + internal static class LangUtils + { + + // There are more characters, so far this is enough + private const string InvalidCharactersRegex = @" |@|,|:|\||;|\||\(|\)|\[|\]|\."; + + public static bool MakeValidMemberName(string memberName, string prefix, out string validMemberName) + { + bool changedMemberName = false; + validMemberName = memberName; + + if (char.IsDigit(memberName[0])) + { + validMemberName = $"{prefix}{memberName}"; + changedMemberName = true; + } + + if (RemoveInvalidCharacters(validMemberName, out validMemberName)) + changedMemberName = true; + + return changedMemberName; + } + + private static bool RemoveInvalidCharacters(string memberName, out string validMemberName) + { + validMemberName = Regex.Replace(memberName, InvalidCharactersRegex, string.Empty); + return memberName != validMemberName; + } + } +}