Skip to content

Commit

Permalink
feat: Finishing version of documentation generator
Browse files Browse the repository at this point in the history
  • Loading branch information
thygesteffensen committed Nov 23, 2023
1 parent 007ba8f commit 6aefb23
Show file tree
Hide file tree
Showing 2 changed files with 245 additions and 41 deletions.
261 changes: 220 additions & 41 deletions src/EAVFW.Extensions.Docs.Generator/ReadMeDocumentationGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EAVFW.Extensions.Docs.Extractor;
using EAVFW.Extensions.Manifest.SDK;
Expand All @@ -15,6 +16,8 @@ public class ReadMeDocumentationGenerator : IDocumentationGenerator
private IEnumerable<PluginDocumentation> _pluginDocumentations;
private ManifestDefinition _manifestObject;

private ISchemaNameManager _schemaNameManager = new DefaultSchemaNameManager();


public void AddPluginSource(IEnumerable<PluginDocumentation> pluginDocumentations)
{
Expand All @@ -32,23 +35,59 @@ public void AddGeneratedManifest(ManifestDefinition generatedManifest)
_manifestObject = generatedManifest;
}

public async Task Write(FileInfo outputLocation, string component)
private string BuildAttributeDescription(AttributeObjectDefinition attribute, string key)
{
var writer = new StreamWriter(outputLocation.FullName);
var s = "";
var _component = "manifest.loi.json";
if (attribute.AttributeType.Type == "lookup")
{
var lookup = attribute.AttributeType.ReferenceType;
_manifestObject.Entities.FirstOrDefault(x => x.Key == lookup).Value.AdditionalFields
.TryGetValue("moduleSource", out var source);

await WriteTables(writer, component);
await WriteWizards(writer);
await WritePlugins(writer);
if (source.ToString() == _component)
{
s +=
$"Lookup: [{attribute.AttributeType.ReferenceType}](#{_schemaNameManager.ToSchemaName(attribute.AttributeType.ReferenceType)})<br/>";
}
else
{
s += $"Lookup: {attribute.AttributeType.ReferenceType} ({source})<br/>";
}
}

await writer.FlushAsync();
}
if (attribute.AttributeType.Type == "Choice")
{
var defaultOption = attribute.AdditionalFields.TryGetValue("default", out var _default);
var defaultValue = 0;
if (defaultOption)
{
// defaultValue = _default.GetInt32();
}


s += "Options: <br/>";
foreach (var (key1, value) in attribute.AttributeType.Options)
{
s += $"- {value}: {key1}";

if (defaultOption && value.GetInt32() == defaultValue)
{
s += " (default)";
}

s += "<br/>";
}
}

return s + $"_Display name:_ {key}";
}

private async Task WriteTables(TextWriter writer, string component)
public async Task Write(FileInfo outputLocation, string component)
{
await writer.WriteLineAsync("## Tables:\n");
var writer = new StreamWriter(outputLocation.FullName);

var t = new DefaultSchemaNameManager();
await writer.WriteLineAsync($"# Documentation for {component}");

var entitiesToWrite = _manifestObject.Entities;

Expand All @@ -60,64 +99,145 @@ private async Task WriteTables(TextWriter writer, string component)
.ToDictionary(x => x.Key, x => x.Value);
}

await writer.WriteLineAsync("## Table of contents");

var index = 2;
await writer.WriteLineAsync("1. [Class diagram](#class-diagram)");
foreach (var (key, value) in entitiesToWrite)
{
await writer.WriteLineAsync($"{index++}. [{key}](#{_schemaNameManager.ToSchemaName(key)})");
}

await writer.WriteLineAsync("# Class diagram <a name=\"class-diagram\"></a>");
await writer.WriteAsync(EntitiesToClassDiagram(_manifestObject, component));

var ignored = new List<string>
{ "Modified On", "Modified By", "Created By", "Created On", "Row Version", "Owner" };

foreach (var (key, value) in entitiesToWrite)
{
_logicalNameLookup[t.ToSchemaName(key)] = key;
await writer.WriteLineAsync($"## {key} <a name=\"{_schemaNameManager.ToSchemaName(key)}\"></a>");

await writer.WriteLineAsync(value.Description);

await WriteAttributes(writer, value, ignored);

await WritePlugins(writer, key);

await writer.WriteLineAsync($"### {key}");
await writer.WriteLineAsync($"Logical name: `{t.ToSchemaName(key)}`");
await writer.WriteLineAsync($"Plural name: {value.PluralName}");
await writer.WriteLineAsync($"Description: {value.Description}");
await writer.WriteLineAsync("Attributes:");

await WriteWizards(writer, key);
}

await writer.FlushAsync();
}

private async Task WritePlugins(TextWriter writer)
private async Task WriteWizards(StreamWriter writer, string key)
{
var groups = _pluginDocumentations.GroupBy(x => x.Entity!.Name);
await writer.WriteLineAsync("## Plugins: ");
foreach (var group in groups)
await writer.WriteLineAsync("### Wizards");

if (_wizards.TryGetValue(key, out var wizard))
{
await writer.WriteLineAsync($"### {group.FirstOrDefault()?.Entity?.Name}");
foreach (var pluginDocumentation in group)
foreach (var (s, wizardDefinition) in wizard.Wizards)
{
await writer.WriteLineAsync($"#### {pluginDocumentation.Name}");
await writer.WriteLineAsync(
$"Entity:\t[{pluginDocumentation.Entity?.Name}](#{pluginDocumentation.Entity?.Name})");
await writer.WriteLineAsync(
$"Context:\t[{pluginDocumentation.Entity?.Name}](#{pluginDocumentation.Entity?.Name})");
await writer.WriteLineAsync("Triggers:\n");
await writer.WriteLineAsync("| Operation | Execution | Mode | Order |");
await writer.WriteLineAsync("|---|---|---|:-:|");
foreach (var reg in pluginDocumentation.PluginRegistrations.OrderBy(x => x.Order))
await writer.WriteLineAsync($"#### {wizardDefinition.Title}");

await writer.WriteLineAsync("\nTriggers:\n");
await writer.WriteLineAsync("| Type | Value |");
await writer.WriteLineAsync("|------|-------|");

foreach (var (k, triggerDefinition) in wizardDefinition.Triggers)
{
await writer.WriteLineAsync($"|{reg.Operation}|{reg.Execution}|{reg.Mode}|{reg.Order}|");
if (string.IsNullOrWhiteSpace(triggerDefinition.Form))
await writer.WriteLineAsync($"| Ribbon | {triggerDefinition.Ribbon} |");
else
await writer.WriteLineAsync($"| Form | {triggerDefinition.Form} |");
}

await writer.WriteLineAsync("\n**Summary:**");
await writer.WriteLineAsync("\nTabs:\n");
await writer.WriteLineAsync("| Tab | Visible | OnTransitionIn | OnTransitionOut |");
await writer.WriteLineAsync("| -- | -- | -- | -- |");

foreach (var (key1, tabDefinition) in wizardDefinition.Tabs)
{
await writer.WriteLineAsync(
$"| {key1} | {GetVisibleString(tabDefinition.Visible.ToString())} | {GetTransitionString(tabDefinition.OnTransitionIn)} | {GetTransitionString(tabDefinition.OnTransitionOut)} |");
}
}
}
else
{
await writer.WriteLineAsync("_No wizards_");
}
}

await writer.WriteLineAsync(SanitizeSummary(pluginDocumentation.Summary));
private string GetVisibleString(string visible)
{
return string.IsNullOrWhiteSpace(visible) ? "" : $"`{visible}`";
}

await writer.WriteLineAsync();
private string GetTransitionString(TransitionDefinition transitionDefinition)
{
if (!string.IsNullOrWhiteSpace(transitionDefinition?.Workflow))
{
var summaryString = "";
if (transitionDefinition.AdditionalData.TryGetValue("x-workflowSummary", out var summary) &&
summary != null)
{
summaryString = $"<br/> Summary: {SanitizeSummary(summary.ToString())}";
}

return $"{transitionDefinition.Workflow} {summaryString}";
}

return "";
}

private async Task WriteWizards(TextWriter writer)
private async Task WritePlugins(StreamWriter writer, string key)
{
await writer.WriteLineAsync("## Wizards:");
await writer.WriteLineAsync("### Plugins");

foreach (var (key, value) in _wizards.Where(x => x.Value.Wizards.Count > 0))
var plugins = _pluginDocumentations.Where(x => x.Entity.Name == _schemaNameManager.ToSchemaName(key))
.ToList();
if (!plugins.Any()) await writer.WriteLineAsync("_No plugins_");
foreach (var pluginDocumentation in plugins)
{
await writer.WriteLineAsync($"### {key}");
await writer.WriteLineAsync($"#### {pluginDocumentation.Name}");

await writer.WriteLineAsync(SanitizeSummary(pluginDocumentation.Summary));

await writer.WriteLineAsync();

foreach (var (wizardKey, wizardDefinition) in value.Wizards)
await writer.WriteLineAsync(
$"Entity:\t[{pluginDocumentation.Entity?.Name}](#{pluginDocumentation.Entity?.Name})");
await writer.WriteLineAsync(
$"Context:\t[{pluginDocumentation.Entity?.Name}](#{pluginDocumentation.Entity?.Name})");

await writer.WriteLineAsync();
await writer.WriteLineAsync("| Operation | Execution | Mode | Order |");
await writer.WriteLineAsync("|---|---|---|:-:|");
foreach (var reg in pluginDocumentation.PluginRegistrations.OrderBy(x => x.Order))
{
await writer.WriteLineAsync($"#### {wizardKey}");
await writer.WriteLineAsync($"|{reg.Operation}|{reg.Execution}|{reg.Mode}|{reg.Order}|");
}
}
}

private async Task WriteAttributes(StreamWriter writer, EntityDefinition value, List<string> ignored)
{
await writer.WriteLineAsync("### Attributes");

await writer.WriteLineAsync("| Name | Type | Details |");
await writer.WriteLineAsync("|------|------|---------|");

foreach (var (s, attributeBase) in value.Attributes.Where(x => !ignored.Contains(x.Key)))
{
if (attributeBase is AttributeObjectDefinition attributeDefinition)
{
await writer.WriteLineAsync(
$"| {_schemaNameManager.ToSchemaName(s)} | {attributeDefinition.AttributeType.Type} | {BuildAttributeDescription(attributeDefinition, s)} |");
}
}
}

private string SanitizeSummary(string summary)
{
Expand Down Expand Up @@ -151,5 +271,64 @@ private string TransformTag(string tag, Dictionary<string, string> properties)

return $"[{value ?? key}](#{key})";
}

private string EntitiesToClassDiagram(ManifestDefinition manifest, string component)
{
var diagramBuilder = new StringBuilder();

diagramBuilder.AppendLine("```mermaid");
diagramBuilder.AppendLine("classDiagram");
diagramBuilder.AppendLine($"\tnote \"Class diagram for {component}\"");

var t = new DefaultSchemaNameManager();

var entities = manifest.Entities.Where(entity =>
entity.Value.AdditionalFields.ContainsKey("moduleSource") &&
entity.Value.AdditionalFields["moduleSource"].ToString() == component);

var ignored = new List<string>
{ "Modified On", "Modified By", "Created By", "Created On", "Row Version", "Owner" };

foreach (var (key, value) in entities)
{
var attributes = value.Attributes.Where(x => !ignored.Contains(x.Key)).ToList();

diagramBuilder.AppendLine($"\tclass {t.ToSchemaName(key)}[\"{key}\"]{{");
foreach (var (s, attributeDefinitionBase) in attributes)
{
if (attributeDefinitionBase is AttributeObjectDefinition o)
{
// Argmunt
if (o.AttributeType.Type == "lookup")
{
diagramBuilder.AppendLine($"\t\t+{t.ToSchemaName(o.AttributeType.ReferenceType)} {s}");
}
else
{
diagramBuilder.AppendLine($"\t\t+{o.AttributeType.Type} {s}");
}
}
}

diagramBuilder.AppendLine("\t}");

var relations = new HashSet<string>();
foreach (var (_, attributeDefinitionBase) in attributes)
{
if (attributeDefinitionBase is AttributeObjectDefinition o && o.AttributeType.Type == "lookup")
{
relations.Add($"\t{o.AttributeType.ReferenceType} <-- {key}");
}
}

foreach (var relation in relations)
{
diagramBuilder.AppendLine(relation);
}
}

diagramBuilder.AppendLine("```");
return diagramBuilder.ToString();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# EAVFW Documentation Generator

The process of generating documentation for EAVFW is a two step process.

1. Extract information from metadata and code
2. Generate documentation from sources.

The process is divided in two steps and seperated by JSON files. The makes it possible to enrich the source files and to create a custom generator without extracting information twice.

## Extract information

`eavfw docs extract -h`

The metadata extract for both plugins and wizards are saved as JSON files.

Plugins is a single file and `PluginDocumentation` can be used to deserialize the document.
Wizards are saved in a directory `wizards` where each JSON file is a `EntityDefinition` enrich with extracted documentation from Workflow.

OnTransitionIn, OnTransitionOut and Actions with workflows are enriched with `x-workflowSummary` with the doc-string for the given class.

## Generate information

The interface `IDocumentationGenerator` depicts how the source can be loaded and at the end written. `EAVFW.Extensions.Docs.Generator` implement a simple markdown generator, that writes all the content to a single markdown file.

This is the default invoked in `eavfw docs generate`.

0 comments on commit 6aefb23

Please sign in to comment.