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
57 changes: 43 additions & 14 deletions src/ProjGraph.Cli/Commands/VisualizeCommand.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using ProjGraph.Cli.Rendering;
using ProjGraph.Core.Models;
using ProjGraph.Lib.Core.Abstractions;
using ProjGraph.Lib.ProjectGraph.Application;
using ProjGraph.Lib.ProjectGraph.Rendering;
using Spectre.Console;
using Spectre.Console.Cli;
using System.ComponentModel;
Expand All @@ -16,11 +16,11 @@ namespace ProjGraph.Cli.Commands;
/// <remarks>
/// The <see cref="VisualizeCommand"/> class is an asynchronous command that uses the <see cref="Settings"/> class
/// to configure the path to the solution or project file and the desired output format. It processes the input
/// and renders the structure in the specified format (tree or mermaid).
/// and renders the structure in the specified format (flat, tree or mermaid).
/// </remarks>
public sealed class VisualizeCommand(
IGraphService graphService,
IDiagramRenderer<SolutionGraph> mermaidRenderer,
IEnumerable<IDiagramRenderer<SolutionGraph>> renderers,
IOutputConsole console)
: AsyncCommand<VisualizeCommand.Settings>
{
Expand All @@ -42,16 +42,16 @@ public sealed class Settings : CommandSettings

/// <summary>
/// Gets or sets the output format for the visualization.
/// Supported formats are "tree" and "mermaid".
/// Supported formats are "flat", "tree", and "mermaid".
/// </summary>
[CommandOption("-f|--format")]
[Description("The output format (tree, mermaid)")]
[DefaultValue("tree")]
public string Format { get; init; } = "tree";
[Description("The output format (flat, tree, mermaid)")]
[DefaultValue("mermaid")]
public string Format { get; private set; } = "mermaid";

/// <summary>
/// Validates the settings provided for the command.
/// Ensures that the specified path exists, is valid, and that the format is either "tree" or "mermaid".
/// Ensures that the specified path exists, is valid, and that the format is "flat", "tree" or "mermaid".
/// </summary>
/// <returns>
/// A <see cref="ValidationResult"/> indicating whether the settings are valid.
Expand All @@ -68,9 +68,10 @@ public override ValidationResult Validate()
return ValidationResult.Error($"File not found: {Path}");
}

if (Format != "tree" && Format != "mermaid")
Format = Format.ToLowerInvariant();
if (Format != "flat" && Format != "tree" && Format != "mermaid")
{
return ValidationResult.Error("Format must be 'tree' or 'mermaid'");
return ValidationResult.Error("Format must be 'flat', 'tree' or 'mermaid'");
}

return ValidationResult.Success();
Expand All @@ -79,7 +80,7 @@ public override ValidationResult Validate()

/// <summary>
/// Executes the command asynchronously, analyzing the specified solution or project file and rendering its structure
/// in the specified format (tree or mermaid).
/// in the specified format (flat, tree or mermaid).
/// </summary>
/// <param name="context">
/// The command context containing information about the execution environment.
Expand All @@ -106,19 +107,26 @@ public override async Task<int> ExecuteAsync(
console.WriteInfo($"Analyzing {settings.Path}...");

var graph = await Task.Run(() => graphService.BuildGraph(settings.Path), cancellationToken);
console.WriteLine(mermaidRenderer.Render(graph));
console.WriteLine(GetRenderer(settings.Format).Render(graph));
}
else
{
// We'll keep AnsiConsole.Status for now as it's a CLI UI feature,
// but we use the service for the final render if we refactor it.
SolutionGraph? graph = null;
await AnsiConsole.Status()
.Spinner(Spinner.Known.Dots)
.StartAsync($"Analyzing [blue]{settings.Path}[/]...", async _ =>
{
var graph = await Task.Run(() => graphService.BuildGraph(settings.Path), cancellationToken);
TreeRenderer.Render(graph);
graph = await Task.Run(() => graphService.BuildGraph(settings.Path), cancellationToken);
});

if (graph is null)
{
return 0;
}

console.WriteLine(GetRenderer(settings.Format).Render(graph));
}

return 0;
Expand All @@ -129,4 +137,25 @@ await AnsiConsole.Status()
return 1;
}
}

/// <summary>
/// Retrieves the appropriate diagram renderer based on the specified format.
/// </summary>
/// <param name="format">The desired output format (e.g., "flat", "tree", "mermaid").</param>
/// <returns>
/// An instance of <see cref="IDiagramRenderer{T}"/> that matches the specified format.
/// </returns>
/// <exception cref="ArgumentException">
/// Thrown when an unsupported format is specified.
/// </exception>
private IDiagramRenderer<SolutionGraph> GetRenderer(string format)
{
return format.ToLowerInvariant() switch
{
"mermaid" => renderers.OfType<MermaidGraphRenderer>().First(),
"tree" => renderers.OfType<TreeGraphRenderer>().First(),
"flat" => renderers.OfType<FlatGraphRenderer>().First(),
_ => throw new ArgumentException($"Unsupported format: {format}")
};
}
}
4 changes: 2 additions & 2 deletions src/ProjGraph.Cli/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public static int Main(string[] args)
config.AddCommand<VisualizeCommand>("visualize")
.WithDescription("Visualize the dependency graph of a solution or project")
.WithExample("visualize", "MySolution.sln")
.WithExample("visualize", "MySolution.sln", "--format", "mermaid");
.WithExample("visualize", "MySolution.sln", "--format", "tree");

config.AddCommand<ErdCommand>("erd")
.WithDescription("Generate a Mermaid ERD for an Entity Framework Core DbContext")
Expand All @@ -35,7 +35,7 @@ public static int Main(string[] args)
config.AddCommand<ClassDiagramCommand>("classdiagram")
.WithDescription("Generate a Mermaid Class Diagram for a C# file")
.WithExample("classdiagram", "Services/UserService.cs")
.WithExample("classdiagram", "Models/User.cs", "--inheritance", "--dependencies");
.WithExample("classdiagram", "Models/User.cs", "--inheritance", "--dependencies", "--depth", "10");
});

return app.Run(args);
Expand Down
185 changes: 0 additions & 185 deletions src/ProjGraph.Cli/Rendering/TreeRenderer.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ public string Render(ClassModel model)
var sb = new StringBuilder();
sb.AppendLine("```mermaid");

if (!string.IsNullOrEmpty(model.Title))
if (!string.IsNullOrWhiteSpace(model.Title))
{
sb.AppendLine("---");
sb.AppendLine($"title: {model.Title}");
Expand Down
18 changes: 15 additions & 3 deletions src/ProjGraph.Lib.Core/Infrastructure/SpectreOutputConsole.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,22 @@ public class SpectreOutputConsole : IOutputConsole
/// <summary>
/// Gets an <see cref="IAnsiConsole"/> that writes to the current standard error stream.
/// </summary>
private static IAnsiConsole Stderr => AnsiConsole.Create(new AnsiConsoleSettings
private static IAnsiConsole Stderr
{
Out = new AnsiConsoleOutput(Console.Error)
});
get
{
var globalConsole = AnsiConsole.Console;
var console = AnsiConsole.Create(new AnsiConsoleSettings
{
Ansi = globalConsole.Profile.Capabilities.Ansi ? AnsiSupport.Yes : AnsiSupport.No,
ColorSystem = ColorSystemSupport.Detect,
Out = new AnsiConsoleOutput(Console.Error)
});
console.Profile.Capabilities.Unicode = globalConsole.Profile.Capabilities.Unicode;
console.Profile.Width = globalConsole.Profile.Width;
return console;
}
}

/// <summary>
/// Writes a message to the console without a newline.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public string Render(EfModel model)
var sb = new StringBuilder();
sb.AppendLine("```mermaid");

if (!string.IsNullOrEmpty(model.ContextName))
if (!string.IsNullOrWhiteSpace(model.ContextName))
{
sb.AppendLine("---");
sb.AppendLine($"title: {model.ContextName}");
Expand Down
11 changes: 10 additions & 1 deletion src/ProjGraph.Lib.ProjectGraph/DependencyInjection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,16 @@ public static IServiceCollection AddProjGraphProjectGraph(this IServiceCollectio
{
services.AddSingleton<BuildGraphUseCase>();
services.AddSingleton<IGraphService, GraphService>();
services.AddSingleton<IDiagramRenderer<SolutionGraph>, MermaidGraphRenderer>();

// Renderers
services.AddTransient<TreeGraphRenderer>();
services.AddTransient<FlatGraphRenderer>();
services.AddTransient<MermaidGraphRenderer>();

// Register as interface for collection injection
services.AddTransient<IDiagramRenderer<SolutionGraph>>(sp => sp.GetRequiredService<TreeGraphRenderer>());
services.AddTransient<IDiagramRenderer<SolutionGraph>>(sp => sp.GetRequiredService<FlatGraphRenderer>());
services.AddTransient<IDiagramRenderer<SolutionGraph>>(sp => sp.GetRequiredService<MermaidGraphRenderer>());

return services;
}
Expand Down
Loading