Skip to content
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
27 changes: 23 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@ Usage:
mermaid-graph [options]

Options:
--path <path> Full path to the solution (*.sln) or project (*.csproj) file that will be mapped.
--version Show version information
-?, -h, --help Show help and usage information
```
--path <path> Full path to the solution (*.sln) or project (*.csproj) file that will be mapped.
--type <Class|Graph> The type of diagram to generate (e.g., Graph or Class). [default: Graph]
--version Show version information
-?, -h, --help Show help and usage information
```

## Example output from this solution

You can run the following command to generate a class diagram for this solution:

```powershell
.\mermaid-graph.exe --path "MermaidGraph.NET.sln" --type Class
```

This will generate a mermaid graph in the console output, which can be piped to a file and used in a markdown document.

```mermaid
---
title: MermaidGraph.NET.sln
Expand Down Expand Up @@ -78,6 +87,16 @@ classDiagram
version 6.0.4
}
MermaidGraphTests ..> coverlet.msbuild
class Microsoft.ClearScript.V8{
type NuGet
version 7.4.5
}
MermaidGraphTests ..> Microsoft.ClearScript.V8
class Microsoft.ClearScript.V8.Native.win-x64{
type NuGet
version 7.4.5
}
MermaidGraphTests ..> Microsoft.ClearScript.V8.Native.win-x64
class Microsoft.NET.Test.Sdk{
type NuGet
version 17.13.0
Expand Down
135 changes: 16 additions & 119 deletions mermaid-graph/Commands.cs
Original file line number Diff line number Diff line change
@@ -1,143 +1,40 @@
using System.Text;
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;
using Microsoft.Build.Locator;
using MermaidGraph.Diagrams;

namespace MermaidGraph;

/// <summary>
/// The commands that can be run by `mermaid-graph`
/// The commands that can be run by `mermaid-graph`.
/// </summary>
public class Commands
{
public const string MermaidBegin = Fence + "mermaid";
public const string Fence = "```";

private readonly StringBuilder _graph;

/// <summary>
/// Initialize the graph output
/// </summary>
public Commands()
{
_graph = new StringBuilder();

// Ensure MSBuild is registered
if (!MSBuildLocator.IsRegistered)
{
MSBuildLocator.RegisterDefaults();
}
}

/// <summary>
/// Generate the dependency graph of a Visual Studio Project.
/// </summary>
/// <param name="file">`.csproj` file.</param>
public string Project(FileInfo file)
/// <param name="diagramType"></param>
public static string Project(FileInfo file, DiagramType diagramType = DiagramType.Graph)
{
Header(file.Name);
using var projectCollection = new ProjectCollection();
var project = projectCollection.LoadProject(file.FullName);
GraphProject(project);
_graph.AppendLine(Fence);
var graph = _graph.ToString();

// Cleanup
_graph.Clear();
projectCollection.UnloadAllProjects();

return graph;
var graph = GetGraphType(diagramType);

return graph.Project(file);
}

/// <summary>
/// Generate the dependency graph of a Visual Studio Solution.
/// </summary>
/// <param name="file">`.sln` file.</param>
public string Solution(FileInfo file)
/// <param name="diagramType"></param>
public static string Solution(FileInfo file, DiagramType diagramType = DiagramType.Graph)
{
Header(file.Name);
var solutionFile = SolutionFile.Parse(file.FullName);
var solutionName = Path.GetFileNameWithoutExtension(file.Name);
var solutionId = $"{solutionName}";
_graph.AppendLine($$"""
class {{solutionName}}{
type solution
}
""");

using var projectCollection = new ProjectCollection();
var graph = GetGraphType(diagramType);

foreach (var project in solutionFile.ProjectsInOrder)
{
if (project.ProjectType != SolutionProjectType.KnownToBeMSBuildFormat) continue;

var projectPath = project.AbsolutePath;
var projectName = Path.GetFileNameWithoutExtension(projectPath);
_graph.AppendLine($" {solutionId} --> {projectName}");
var projectFile = new FileInfo(projectPath);
if (projectFile.Exists)
{
var referenceProject = projectCollection.LoadProject(projectFile.FullName);
GraphProject(referenceProject);
}
}

_graph.AppendLine(Fence);
var graph = _graph.ToString();

// Cleanup
_graph.Clear();
projectCollection.UnloadAllProjects();

return graph;
}

private void Header(string title)
{
_graph.AppendLine(MermaidBegin);
_graph.AppendLine($"""
---
title: {title}
config:
class:
hideEmptyMembersBox: true
---
""");

_graph.AppendLine("classDiagram");
return graph.Solution(file);
}

private void GraphProject(Project project)
private static IMermaidDiagram GetGraphType(DiagramType diagramType) => diagramType switch
{
var projectName = Path.GetFileNameWithoutExtension(project.FullPath);
var type = project.GetPropertyValue("OutputType");
var targetFramework = project.GetPropertyValue("TargetFramework") ?? project.GetPropertyValue("TargetFrameworks");
_graph.AppendLine($$"""
class {{projectName}}{
type {{type}}
target {{targetFramework}}
}
""");

foreach (var item in project.GetItems("ProjectReference"))
{
var refPath = item.EvaluatedInclude;
var refName = Path.GetFileNameWithoutExtension(refPath);
_graph.AppendLine($" {projectName} ..> {refName}");
}

foreach (var item in project.GetItems("PackageReference"))
{
var packageName = item.EvaluatedInclude;
var version = item.GetMetadataValue("Version");
_graph.AppendLine($$"""
class {{packageName}}{
type NuGet
version {{version}}
}
""");

_graph.AppendLine($" {projectName} ..> {packageName}");
}
}
DiagramType.Class => new ClassDiagram(),
DiagramType.Graph => new GraphDiagram(),
_ => throw new NotImplementedException($"Option not supported: {diagramType}"),
};
}
105 changes: 105 additions & 0 deletions mermaid-graph/Diagrams/ClassDiagram.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using Microsoft.Build.Construction;
using Microsoft.Build.Evaluation;

namespace MermaidGraph.Diagrams;

internal class ClassDiagram : MermaidDiagram

Check warning on line 6 in mermaid-graph/Diagrams/ClassDiagram.cs

View workflow job for this annotation

GitHub Actions / pack

Type 'ClassDiagram' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)

Check warning on line 6 in mermaid-graph/Diagrams/ClassDiagram.cs

View workflow job for this annotation

GitHub Actions / pack

Type 'ClassDiagram' can be sealed because it has no subtypes in its containing assembly and is not externally visible (https://learn.microsoft.com/dotnet/fundamentals/code-analysis/quality-rules/ca1852)
{
/// <inheritdoc />
public override void Header(string title)
{
base.Header(title);
Graph.AppendLine("classDiagram");
}

/// <summary>
/// Generate the dependency graph of a Visual Studio Project.
/// </summary>
/// <param name="file">`.csproj` file.</param>
public override string Project(FileInfo file)
{
Header(file.Name);
using var projectCollection = new ProjectCollection();
var project = projectCollection.LoadProject(file.FullName);
GraphProject(project);
Graph.AppendLine(Fence);

projectCollection.UnloadAllProjects();

return Graph.ToString();
}

/// <summary>
/// Generate the dependency graph of a Visual Studio Solution.
/// </summary>
/// <param name="file">`.sln` file.</param>
public override string Solution(FileInfo file)
{
Header(file.Name);
var solutionFile = SolutionFile.Parse(file.FullName);
var solutionName = Path.GetFileNameWithoutExtension(file.Name);
var solutionId = $"{solutionName}";
Graph.AppendLine($$"""
class {{solutionName}}{
type solution
}
""");

using var projectCollection = new ProjectCollection();

foreach (var project in solutionFile.ProjectsInOrder)
{
if (project.ProjectType != SolutionProjectType.KnownToBeMSBuildFormat) continue;

var projectPath = project.AbsolutePath;
var projectName = Path.GetFileNameWithoutExtension(projectPath);
Graph.AppendLine($" {solutionId} --> {projectName}");
var projectFile = new FileInfo(projectPath);
if (projectFile.Exists)
{
var referenceProject = projectCollection.LoadProject(projectFile.FullName);
GraphProject(referenceProject);
}
}

Graph.AppendLine(Fence);

projectCollection.UnloadAllProjects();

return Graph.ToString();
}

private void GraphProject(Project project)
{
var projectName = Path.GetFileNameWithoutExtension(project.FullPath);
var type = project.GetPropertyValue("OutputType");
var targetFramework = project.GetPropertyValue("TargetFramework") ?? project.GetPropertyValue("TargetFrameworks");
Graph.AppendLine($$"""
class {{projectName}}{
type {{type}}
target {{targetFramework}}
}
""");

foreach (var item in project.GetItems("ProjectReference"))
{
var refPath = item.EvaluatedInclude;
var refName = Path.GetFileNameWithoutExtension(refPath);
Graph.AppendLine($" {projectName} ..> {refName}");
}

foreach (var item in project.GetItems("PackageReference"))
{
var packageName = item.EvaluatedInclude;
var version = item.GetMetadataValue("Version");
Graph.AppendLine($$"""
class {{packageName}}{
type NuGet
version {{version}}
}
""");

Graph.AppendLine($" {projectName} ..> {packageName}");
}
}
}
17 changes: 17 additions & 0 deletions mermaid-graph/Diagrams/DiagramType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
namespace MermaidGraph.Diagrams;

/// <summary>
/// Specifies the type of mermaid diagram to generate.
/// </summary>
public enum DiagramType
{
/// <summary>
/// Represents a general dependency graph.
/// </summary>
Graph,

/// <summary>
/// Represents a class diagram.
/// </summary>
Class
}
Loading
Loading