Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
cf0ba16
Add CommandLinerParser package
boma96 Aug 12, 2022
2f25eee
Rename class
boma96 Aug 13, 2022
4936d04
Add YamlDotNet package
boma96 Aug 13, 2022
297ec15
Integrate command line parser library
boma96 Aug 13, 2022
e24abc6
Rename method
boma96 Aug 13, 2022
d17e523
Remove unnecessary function
boma96 Aug 13, 2022
6072a95
Rename properties
boma96 Aug 13, 2022
9206b5d
Remove unnecessary property
boma96 Aug 13, 2022
0e4429a
Remove unnecessary class
boma96 Aug 13, 2022
8957a71
Pass headers to client
boma96 Aug 13, 2022
d67e842
Remove unnecessary newline
boma96 Aug 13, 2022
edc0c78
Add exception handling
boma96 Aug 13, 2022
ba74c50
Rename option
boma96 Aug 13, 2022
ddc9d2e
Specify default value for api path
boma96 Aug 13, 2022
c9456ce
Validate configuration
boma96 Aug 13, 2022
2980d0e
Add comment
boma96 Aug 14, 2022
bbd3c14
Make method sync
boma96 Aug 14, 2022
8e00de7
Change copyright line
boma96 Aug 15, 2022
d17b1fb
Read version from constant string
boma96 Aug 15, 2022
fc1e20c
Perform IsNullOrEmpty check instead of null check only
boma96 Aug 15, 2022
ac4bc26
Add package used for code generation
boma96 Aug 15, 2022
27eca6f
Add class for generating task models
boma96 Aug 15, 2022
095e1ad
Change the way note is build
boma96 Aug 15, 2022
480a759
Change the way properties are added, fix comment generation
boma96 Aug 16, 2022
4afdb46
Implement new way of scaffolding for workflows
boma96 Aug 16, 2022
f80cf3a
Add using
boma96 Aug 16, 2022
f8e6ca7
Implement scaffolding using roslyn
boma96 Aug 16, 2022
4fbda2c
Remve unnecessary property
boma96 Aug 16, 2022
28210b5
Create directory sctructure if it does not exists
boma96 Aug 16, 2022
c5b0748
Remove unnecessary lines
boma96 Aug 16, 2022
63e96e8
Remove unnecessary comment
boma96 Aug 16, 2022
a9926c7
Add stack trace
boma96 Aug 16, 2022
d53b3bc
Merge master into feat/master/multiple-files
boma96 Aug 16, 2022
004f964
Generate OriginalName attribute
boma96 Aug 22, 2022
8092cea
Specify OriginalName for task and classes
boma96 Aug 22, 2022
787b9d2
Add MemberNamePrefix constant
boma96 Aug 22, 2022
9168268
Add readonly modifier, use arrow syntax for some methods
boma96 Aug 22, 2022
18107e8
Remove unnecessary newline
boma96 Aug 22, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/ConductorSharp.Toolkit/ConductorSharp.Toolkit.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
<ItemGroup>
<PackageReference Include="Autofac.Extensions.DependencyInjection" Version="8.0.0" />
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.2.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="6.0.0" />
<PackageReference Include="Serilog.Extensions.Logging" Version="3.1.0" />
<PackageReference Include="YamlDotNet" Version="12.0.0" />
Expand Down
2 changes: 2 additions & 0 deletions src/ConductorSharp.Toolkit/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ private static async Task RunToolkit(ToolkitOptions options)
catch (Exception ex)
{
Console.Error.WriteLine($"Exception occured with message: {ex.Message}");
Console.Error.WriteLine("Stack trace:");
Console.Error.WriteLine(ex.StackTrace);
}
}

Expand Down
169 changes: 71 additions & 98 deletions src/ConductorSharp.Toolkit/Service/ScaffoldingService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public class ScaffoldingService : IScaffoldingService
private readonly ILogger<ScaffoldingService> _logger;
private readonly ScaffoldingConfig _config;

// Prefix prepended if member name is invalid
private const string MemberNamePrefix = "A";

public ScaffoldingService(IMetadataService metadataService, IOptions<ScaffoldingConfig> options, ILogger<ScaffoldingService> logger)
{
_metadataService = metadataService;
Expand All @@ -26,58 +29,47 @@ public ScaffoldingService(IMetadataService metadataService, IOptions<Scaffolding
public async Task Scaffold()
{
var workflowDefinitions = await _metadataService.GetAllWorkflowDefinitions();
var workflowCollectionBuilder = new StringBuilder();
var workflowDirectory = Path.Combine(_config.Destination, "Workflows");
Directory.CreateDirectory(workflowDirectory);
foreach (var workflowDefinition in workflowDefinitions)
{
var workflowClass = CreateWorkflowClass(workflowDefinition);
(var contents, var modelClassName) = CreateWorkflowClass(workflowDefinition);

if (workflowClass != null)
workflowCollectionBuilder.Append(workflowClass);
if (contents != null)
{
var filePath = Path.Combine(workflowDirectory, $"{modelClassName}.scaff.cs");
File.WriteAllText(filePath, contents);
}
}

var taskDefinitions = await _metadataService.GetAllTaskDefinitions();

var taskCollectionBuilder = new StringBuilder();
var tasksDirectory = Path.Combine(_config.Destination, "Tasks");
Directory.CreateDirectory(tasksDirectory);
foreach (var taskDefinition in taskDefinitions)
{
var taskClass = CreateTaskClass(taskDefinition);
(var contents, var modelClassName) = CreateTaskClass(taskDefinition);

if (taskClass != null)
taskCollectionBuilder.Append(taskClass);
if (contents != null)
{
var filePath = Path.Combine(tasksDirectory, $"{modelClassName}.scaff.cs");
File.WriteAllText(filePath, contents);
}
}

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)
public (string contents, string modelClassName) 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))
if (LangUtils.MakeValidMemberName(name, MemberNamePrefix, out name))
{
note = "The autogenerated name has been changed because it is invalid C# member name";
}

var modelGenerator = new TaskModelGenerator(_config.BaseNamespace + ".Workflows", name, TaskModelGenerator.ModelType.Workflow)
{
OriginalName = workflowDefinition.Name
};
string workflowDescription = workflowDefinition.Description;
string[] labels;

Expand All @@ -97,37 +89,41 @@ public string CreateWorkflowClass(WorkflowDefinition workflowDefinition)
_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 inputPropData = new TaskModelGenerator.PropertyData();

var value = param.Value.SelectToken("value")?.ToString(Newtonsoft.Json.Formatting.None);
var type = param.Value.SelectToken("type")?.Value<string>();
var description = param.Value.SelectToken("description")?.Value<string>();

if (type == null)
type = "string";

AppendXmlComment(inputLinesBuilder, "originalName", param.Key);
inputPropData.XmlComments["originalName"] = param.Key;

if (!string.IsNullOrEmpty(description))
AppendXmlComment(inputLinesBuilder, "summary", value);
inputPropData.XmlComments["summary"] = description;

switch (type)
{
case "string":
case "integer":
if (!string.IsNullOrEmpty(value))
{
AppendXmlComment(inputLinesBuilder, "remark", $"Example: {value}");
inputPropData.XmlComments["remark"] = $"Example: {value}";
}
AppendClassProperty(inputLinesBuilder, "dynamic", param.Key);
DefinePropertyParams(inputPropData, "dynamic", param.Key);
modelGenerator.AddInputProperty(inputPropData);

break;
case "toggle":
AppendClassProperty(inputLinesBuilder, "dynamic", param.Key);
DefinePropertyParams(inputPropData, "dynamic", param.Key);
modelGenerator.AddInputProperty(inputPropData);
break;
case "select":
var optionsToken = param.Value.SelectToken("options");
Expand All @@ -139,9 +135,10 @@ public string CreateWorkflowClass(WorkflowDefinition workflowDefinition)
options = optionsArray.ToObject<string[]>();
}
if (options != null && options.Length > 0)
AppendXmlComment(inputLinesBuilder, "remark", $"Options: {string.Join(',', options)}");
inputPropData.XmlComments["remark"] = $"Options: {string.Join(',', options)}";

AppendClassProperty(inputLinesBuilder, "dynamic", param.Key);
DefinePropertyParams(inputPropData, "dynamic", param.Key);
modelGenerator.AddInputProperty(inputPropData);
break;
default:
break;
Expand All @@ -164,81 +161,59 @@ public string CreateWorkflowClass(WorkflowDefinition workflowDefinition)
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', ','));
modelGenerator.AddXmlComment("summary", workflowDescription?.Replace('\n', ','));
modelGenerator.AddXmlComment("originalName", workflowDefinition.Name);
modelGenerator.AddXmlComment("ownerApp", workflowDefinition.OwnerApp);
modelGenerator.AddXmlComment("ownerEmail", workflowDefinition.OwnerEmail);
modelGenerator.AddXmlComment("note", note);

if (_config.Dryrun)
return null;

return lines;
}
return (null, null);

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; }}");
return (modelGenerator.Build(), name);
}

private void AppendXmlComment(StringBuilder linesBuilder, string tag, string value)
private void DefinePropertyParams(TaskModelGenerator.PropertyData propData, string type, string originalName)
{
linesBuilder.AppendLine($" /// <{tag}>");
linesBuilder.AppendLine($" /// {value}");
linesBuilder.AppendLine($" /// </{tag}>");
LangUtils.MakeValidMemberName(SnakeCaseUtil.ToPascalCase(originalName), MemberNamePrefix, out var propertyName);
propData.OriginalName = originalName;
propData.Name = propertyName;
propData.Type = type;
}

public string CreateTaskClass(TaskDefinition taskDefinition)
public (string contents, string modelClassName) 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))
if (LangUtils.MakeValidMemberName(name, MemberNamePrefix, out name))
{
note = "The autogenerated name has been prepended because it starts with a digit character.";
}

var inputLinesBuilder = new StringBuilder();
var modelGenerator = new TaskModelGenerator(_config.BaseNamespace + ".Tasks", name, TaskModelGenerator.ModelType.Task)
{
OriginalName = taskDefinition.Name
};

foreach (var property in taskDefinition.InputKeys)
{
AppendXmlComment(inputLinesBuilder, "originalName", property);
AppendClassProperty(inputLinesBuilder, "dynamic", property);
var inputPropData = new TaskModelGenerator.PropertyData();
DefinePropertyParams(inputPropData, "dynamic", property);
inputPropData.XmlComments["originalName"] = property;
modelGenerator.AddInputProperty(inputPropData);
}

var outputLinesBuilder = new StringBuilder();
foreach (var property in taskDefinition.OutputKeys)
{
AppendXmlComment(outputLinesBuilder, "originalName", property);
AppendClassProperty(outputLinesBuilder, "dynamic", property);
var inputPropData = new TaskModelGenerator.PropertyData();
DefinePropertyParams(inputPropData, "dynamic", property);
inputPropData.XmlComments["originalName"] = property;
modelGenerator.AddOutputProperty(inputPropData);
}

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}");
modelGenerator.AddXmlComment("originalName", taskDefinition.Name);
modelGenerator.AddXmlComment("ownerEmail", taskDefinition.OwnerEmail);
modelGenerator.AddXmlComment("node", note);

var description = "";
try
Expand All @@ -259,13 +234,11 @@ public string CreateTaskClass(TaskDefinition taskDefinition)
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', ','));

modelGenerator.AddXmlComment("summary", description.Replace('\n', ','));
if (_config.Dryrun)
return null;
return (null, null);

return lines;
return (modelGenerator.Build(), name);
}
}
}
Loading