Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support for include-exception-stacktrace in verbs-from-options-file #134

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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 docs/templates/releasenotes/yyyy-MM-dd-v-x.y.z.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
## Prerelease 2
- Added support for generating many to many associations in `mermaid-class-diagram-from-csharp`.
- Added support for generating many to many relations in `mermaid-er-diagram-from-csharp`.
- Added support for `include-exception-stacktrace` in `verbs-from-options-file`.
## Prerelease 1 (2024-10-29)
- Fixed an issue where `mermaid-er-diagram-from-efcore` would fail when an input assebly referenced Asp.Net Core.
- Ie the .csproj file contained `<Project Sdk="Microsoft.NET.Sdk.Web">` or `<FrameworkReference Include="Microsoft.AspNetCore.App|All">` or referenced such an assembly.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,51 +16,72 @@ public IReadOnlyList<ErDiagramEntity> GenerateErStructure(Assembly assembly, IRe
}

private static void GeneratieErStructure(IList<ErDiagramEntity> entities, IReadOnlyList<IPropertyFilter> attributeFilters)
{
var enumEntities = new Dictionary<Type, ErDiagramEntity>();
var entityLookup = entities.ToDictionary(x => x.Type, x => x);
foreach (var entity in entities)
{
foreach (var property in entity.Type.GetProperties().Where(p => attributeFilters.All(f => f.Accepts(p))))
{
var propertyType = property.PropertyType;
if (entityLookup.TryGetValue(propertyType, out var fromEntity))
{
var label = property.Name.Replace(fromEntity.Name, string.Empty).ToLower();
fromEntity.AddRelationship(entity, ErDiagramRelationshipCardinality.ExactlyOne, ErDiagramRelationshipCardinality.ExactlyOne, label, property.Name);
}
else if (propertyType.IsGenericType
&& propertyType.GetGenericTypeDefinition().IsErDiagramRelationshipCollection()
&& propertyType.GetGenericArguments().Length == 1)
{
var genericArgumentPropertyType = propertyType.GetGenericArguments()[0];
if (entityLookup.TryGetValue(genericArgumentPropertyType, out var toEntity))
{
var label = property.Name.Contains(toEntity.Name) ? string.Empty : property.Name.ToNormalizedRelationshipLabel();
entity.AddRelationship(toEntity, ErDiagramRelationshipCardinality.ExactlyOne, ErDiagramRelationshipCardinality.ZeroOrMore, label, property.Name);
}
}
else if (property.IsErDiagramAttributePropertyType())
{
var attributeType = property.GetErDiagramAttributeTypeName();
var attributeName = property.Name;
var nullableUnderlyingType = Nullable.GetUnderlyingType(property.PropertyType);
var isNullable = nullableUnderlyingType != null;
var isPrimaryKey = property.CustomAttributes.Any(x => x.IsKeyAttribute());
var enumType = nullableUnderlyingType ?? propertyType;
var isEnum = enumType.AddToEntityAsRelationshipIfEnum(attributeName, isNullable, entity, enumEntities, x => new ErDiagramEntity(x.Name, x));
entity.AddAttribute(new ErDiagramAttribute(attributeType, attributeName, isNullable, isPrimaryKey, isForeignKey: isEnum));
}
}
}
foreach (var entity in entities.Reverse())
{
entity.RemoveBidirectionalRelationshipDuplicates();
}
foreach (var entity in entities)
{
entity.MergeTwoOneToManyIntoOneMayToMany();
}
enumEntities.AppendToEntities(entities);
}
{
var enumEntities = new Dictionary<Type, ErDiagramEntity>();
var entityLookup = entities.ToDictionary(x => x.Type, x => x);
foreach (var entity in entities)
{
GeneratieErStructureForEntity(attributeFilters, enumEntities, entityLookup, entity);
}
RemoveBidirectionalRelationshipDuplicates(entities);
MergeTwoOneToManyIntoOneMayToMany(entities);
enumEntities.AppendToEntities(entities);
}

private static void GeneratieErStructureForEntity(IReadOnlyList<IPropertyFilter> attributeFilters, Dictionary<Type, ErDiagramEntity> enumEntities, Dictionary<Type, ErDiagramEntity> entityLookup, ErDiagramEntity entity)
{
foreach (var property in entity.Type.GetProperties().Where(p => attributeFilters.All(f => f.Accepts(p))))
{
GeneratieErStructureForEntityProperty(enumEntities, entityLookup, entity, property);
}
}

private static void GeneratieErStructureForEntityProperty(Dictionary<Type, ErDiagramEntity> enumEntities, Dictionary<Type, ErDiagramEntity> entityLookup, ErDiagramEntity entity, PropertyInfo? property)
{
property = property.AsNonNull();
var propertyType = property.PropertyType;
if (entityLookup.TryGetValue(propertyType, out var fromEntity))
{
var label = property.Name.Replace(fromEntity.Name, string.Empty).ToLower();
fromEntity.AddRelationship(entity, ErDiagramRelationshipCardinality.ExactlyOne, ErDiagramRelationshipCardinality.ExactlyOne, label, property.Name);
}
else if (propertyType.IsGenericType
&& propertyType.GetGenericTypeDefinition().IsErDiagramRelationshipCollection()
&& propertyType.GetGenericArguments().Length == 1)
{
var genericArgumentPropertyType = propertyType.GetGenericArguments()[0];
if (entityLookup.TryGetValue(genericArgumentPropertyType, out var toEntity))
{
var label = property.Name.Contains(toEntity.Name) ? string.Empty : property.Name.ToNormalizedRelationshipLabel();
entity.AddRelationship(toEntity, ErDiagramRelationshipCardinality.ExactlyOne, ErDiagramRelationshipCardinality.ZeroOrMore, label, property.Name);
}
}
else if (property.IsErDiagramAttributePropertyType())
{
var attributeType = property.GetErDiagramAttributeTypeName();
var attributeName = property.Name;
var nullableUnderlyingType = Nullable.GetUnderlyingType(property.PropertyType);
var isNullable = nullableUnderlyingType != null;
var isPrimaryKey = property.CustomAttributes.Any(x => x.IsKeyAttribute());
var enumType = nullableUnderlyingType ?? propertyType;
var isEnum = enumType.AddToEntityAsRelationshipIfEnum(attributeName, isNullable, entity, enumEntities, x => new ErDiagramEntity(x.Name, x));
entity.AddAttribute(new ErDiagramAttribute(attributeType, attributeName, isNullable, isPrimaryKey, isForeignKey: isEnum));
}
}

private static void RemoveBidirectionalRelationshipDuplicates(IList<ErDiagramEntity> entities)
{
foreach (var entity in entities.Reverse())
{
entity.RemoveBidirectionalRelationshipDuplicates();
}
}

private static void MergeTwoOneToManyIntoOneMayToMany(IList<ErDiagramEntity> entities)
{
foreach (var entity in entities)
{
entity.MergeTwoOneToManyIntoOneMayToMany();
}
}
}
2 changes: 1 addition & 1 deletion src/DryGen/Extensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ public static Type GetVerbOptionsType(this string verb)
var optionsTypes = typeof(Extensions).Assembly.GetTypes().Where(type => VerbAttributeMatches(type, verb)).ToList();
if (optionsTypes.Count == 0)
{
throw new OptionsException($"Unknown verb '{nameof(verb)}'");
throw new OptionsException($"Unknown verb '{verb}'");
}
return optionsTypes.Single();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ private static List<VerbsFromOptionsFileOptionsDocument> ReadOptionsDocumentsFro
throw new OptionsException($"'configuration.options' is mandatory in document #{documentNumber}");
}
optionsDocument.DocumentNumber = documentNumber;
if (options.IncludeExceptionStackTrace) {
optionsDocument.GetConfiguration().GetOptions().AsNonNull().IncludeExceptionStackTrace = true;
}
optionsDocuments.Add(optionsDocument);
yamlParser.TryConsume<DocumentEnd>(out _);
}
Expand Down
63 changes: 20 additions & 43 deletions src/DryGen/Generator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -273,64 +273,41 @@ private int GenerateMermaidErDiagramFromJsonSchema(MermaidErDiagramFromJsonSchem

private int GenerateOptionsFromCommandline(OptionsFromCommandlineOptions options, string[] args)
{
return ExecuteWithOptionsFromFileExceptionHandlingAndHelpDisplay(options, args, "dry-gen options", options =>
{
return OptionsFromCommandlineGenerator.Generate(options);
});
return ExecuteWithOptionsFromFileExceptionHandlingAndHelpDisplay(options, args, "dry-gen options", OptionsFromCommandlineGenerator.Generate);
}

private int GenerateVerbsFromOptionsFile(VerbsFromOptionsFileOptions options)
{
return ExecuteWithExceptionHandlingAndHelpDisplay(options, options =>
{
var optionsDocuments = VerbsFromOptionsFileOptionsDocumentsBuilder.BuildOptionsDocuments(options);
GenerateFromOptionsDocuments(optionsDocuments);
return 0;
return GenerateFromOptionsDocuments(optionsDocuments);
});
}

private void GenerateFromOptionsDocuments(IEnumerable<VerbsFromOptionsFileOptionsDocument> optionsDocuments)
private int GenerateFromOptionsDocuments(IEnumerable<VerbsFromOptionsFileOptionsDocument> optionsDocuments)
{
var exitCode = 0;
foreach (var optionsDocument in optionsDocuments)
{
switch (optionsDocument.GetConfiguration().Verb)
{
case Constants.CsharpFromJsonSchema.Verb:
GenerateCSharpFromJsonSchema(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<CSharpFromJsonSchemaOptions>(), Array.Empty<string>());
break;

case Constants.MermaidC4ComponentDiagramFromDotnetDepsJson.Verb:
GenerateMermaidC4ComponentDiagramFromDotnetDepsJson(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidC4ComponentDiagramFromDotnetDepsJsonOptions>(), Array.Empty<string>());
break;

case Constants.MermaidClassDiagramFromCsharp.Verb:
GenerateMermaidClassDiagramFromCsharp(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidClassDiagramFromCsharpOptions>(), Array.Empty<string>());
break;

case Constants.MermaidClassDiagramFromJsonSchema.Verb:
GenerateMermaidClassDiagramFromJsonSchema(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidClassDiagramFromJsonSchemaOptions>(), Array.Empty<string>());
break;

case Constants.MermaidErDiagramFromCsharp.Verb:
GenerateMermaidErDiagramFromCsharp(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidErDiagramFromCsharpOptions>(), Array.Empty<string>());
break;

case Constants.MermaidErDiagramFromEfCore.Verb:
GenerateMermaidErDiagramFromEfCore(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidErDiagramFromEfCoreOptions>(), Array.Empty<string>());
break;

case Constants.MermaidErDiagramFromJsonSchema.Verb:
GenerateMermaidErDiagramFromJsonSchema(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidErDiagramFromJsonSchemaOptions>(), Array.Empty<string>());
break;

case Constants.OptionsFromCommandline.Verb:
GenerateOptionsFromCommandline(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<OptionsFromCommandlineOptions>(), Array.Empty<string>());
break;

default:
throw new OptionsException($"Unsupported verb '{optionsDocument.GetConfiguration().Verb}' in document #{optionsDocument.DocumentNumber}");
var currentIterationExitCode = 0;
currentIterationExitCode = optionsDocument.GetConfiguration().Verb switch
{
Constants.CsharpFromJsonSchema.Verb => GenerateCSharpFromJsonSchema(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<CSharpFromJsonSchemaOptions>(), []),
Constants.MermaidC4ComponentDiagramFromDotnetDepsJson.Verb => GenerateMermaidC4ComponentDiagramFromDotnetDepsJson(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidC4ComponentDiagramFromDotnetDepsJsonOptions>(), []),
Constants.MermaidClassDiagramFromCsharp.Verb => GenerateMermaidClassDiagramFromCsharp(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidClassDiagramFromCsharpOptions>(), []),
Constants.MermaidClassDiagramFromJsonSchema.Verb => GenerateMermaidClassDiagramFromJsonSchema(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidClassDiagramFromJsonSchemaOptions>(), []),
Constants.MermaidErDiagramFromCsharp.Verb => GenerateMermaidErDiagramFromCsharp(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidErDiagramFromCsharpOptions>(), []),
Constants.MermaidErDiagramFromEfCore.Verb => GenerateMermaidErDiagramFromEfCore(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidErDiagramFromEfCoreOptions>(), []),
Constants.MermaidErDiagramFromJsonSchema.Verb => GenerateMermaidErDiagramFromJsonSchema(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<MermaidErDiagramFromJsonSchemaOptions>(), []),
Constants.OptionsFromCommandline.Verb => GenerateOptionsFromCommandline(optionsDocument.GetConfiguration().GetOptions().AsNonNullOptions<OptionsFromCommandlineOptions>(), []),
_ => throw new OptionsException($"Unsupported verb '{optionsDocument.GetConfiguration().Verb}' in document #{optionsDocument.DocumentNumber}"),
};
if (currentIterationExitCode > 0) {
exitCode = currentIterationExitCode;
}
}
return exitCode;
}

private TOptions GetOptionsFromFileWithCommandlineOptionsAsOverrides<TOptions>(TOptions commandlineOptions, string[] args) where TOptions : CommonOptions
Expand Down
20 changes: 20 additions & 0 deletions src/develop/DryGen.UTests/Features/CommandLine.feature
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,26 @@ Scenario: Print usage to console error without exception stacktrace info when --
And I should find the text " at " in console error
But I should not find the text "NB! You can also add --include-exception-stacktrace to get the stack trace for the exception" in console error

Scenario: Print usage to console error without exception stacktrace info when --include-exception-stacktrace is used and an exception is thrown from verbs-from-options-file
# Uses an invalid verb in options-from-commandline to trigger an exception
Given this content as an options file
# The commandline arguments -f <this filename> will be appended to the command line
"""
configuration:
verb: options-from-commandline
options:
verb: invalid-verb
"""
When I call the program with this command line arguments
| Arg |
| verbs-from-options-file |
| --include-exception-stacktrace |
Then I should get exit code '1'
And I should find the text "ERROR:" in console error
And I should find the text "Rerun the command with --help to get more help information" in console error
And I should find the text " at " in console error
But I should not find the text "NB! You can also add --include-exception-stacktrace to get the stack trace for the exception" in console error

Scenario: Print usage to console error without exception stacktrace info when an well known exception is thrown
Given this .Net depts json input file
"""
Expand Down
16 changes: 16 additions & 0 deletions src/develop/DryGen.UTests/Features/VerbsFromOptionsFile.feature
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,22 @@ Scenario: Should fail for unknown 'verb' in a yaml document with 'options'
Then I should get exit code '1'
And I should find the text "Unknown 'verb' in document #1" in console error

Scenario: Should fail when executing a 'verb' fails
# Uses an invalid verb in options-from-commandline to trigger an exception
Given this content as an options file
# The commandline arguments -f <this filename> will be appended to the command line
"""
configuration:
verb: options-from-commandline
options:
verb: invalid-verb
"""
When I call the program with this command line arguments
| Arg |
| verbs-from-options-file |
Then I should get exit code '1'
And I should find the text "Unknown verb 'invalid-verb'" in console error

Scenario: Should generate output with the verb 'mermaid-c4component-diagram-from-dotnet-deps-json' in the options file
Given this content as an options file
# The commandline arguments -f <this filename> will be appended to the command line
Expand Down