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
3 changes: 3 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@
<PackageVersion Include="NuGet.Core" Version="2.14.0-rtm-832" />
<PackageVersion Include="NuGetValidator" version="2.1.1" />
<PackageVersion Include="SharpZipLib" Version="1.4.2" />
<PackageVersion Include="Spectre.Console" Version="0.54.0" />
<PackageVersion Include="Spectre.Console.Testing" Version="0.54.0" />
<PackageVersion Include="System.Collections.Immutable" Version="8.0.0" />
<PackageVersion Include="System.CommandLine" Version="$(SystemCommandLineVersion)" />
<PackageVersion Include="System.ComponentModel.Composition" Version="$(SystemComponentModelCompositionPackageVersion)" />
Expand Down Expand Up @@ -179,6 +181,7 @@
<_allowBuildFromSourcePackage Include="Microsoft.Extensions.FileSystemGlobbing" />
<_allowBuildFromSourcePackage Include="Microsoft.Web.Xdt" />
<_allowBuildFromSourcePackage Include="Newtonsoft.Json" />
<_allowBuildFromSourcePackage Include="Spectre.Console" />
<_allowBuildFromSourcePackage Include="System.Collections.Immutable" />
<_allowBuildFromSourcePackage Include="System.CommandLine" />
<_allowBuildFromSourcePackage Include="System.ComponentModel.Composition" />
Expand Down
1 change: 1 addition & 0 deletions NuGet.Config
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
<package pattern="nuget.*" />
<package pattern="runtime.*" />
<package pattern="sharpziplib" />
<package pattern="Spectre.*" />
<package pattern="streamjsonrpc" />
<package pattern="system.*" />
<package pattern="Validation" />
Expand Down
1 change: 1 addition & 0 deletions NuGet.sln
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution
build\config.props = build\config.props
Directory.Packages.props = Directory.Packages.props
build\DotNetSdkVersions.txt = build\DotNetSdkVersions.txt
NuGet.Config = NuGet.Config
build\sign.targets = build\sign.targets
spelling.dic = spelling.dic
build\test.targets = build\test.targets
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,30 +7,25 @@
using System.Collections.Generic;
using System.Linq;
using NuGet.Shared;
using Spectre.Console;
using Spectre.Console.Rendering;

namespace NuGet.CommandLine.XPlat.Commands.Why
{
internal static class DependencyGraphPrinter
{
private const ConsoleColor TargetPackageColor = ConsoleColor.Cyan;

// Dependency graph console output symbols
private const string ChildNodeSymbol = "├─ ";
private const string LastChildNodeSymbol = "└─ ";

private const string ChildPrefixSymbol = "│ ";
private const string LastChildPrefixSymbol = " ";
private static readonly Color TargetPackageColor = Color.Cyan;

/// <summary>
/// Prints the dependency graphs for all target frameworks.
/// </summary>
/// <param name="dependencyGraphPerFramework">A dictionary mapping target frameworks to their dependency graphs.</param>
/// <param name="targetPackage">The package we want the dependency paths for.</param>
/// <param name="logger"></param>
public static void PrintAllDependencyGraphs(Dictionary<string, List<DependencyNode>?> dependencyGraphPerFramework, string targetPackage, ILoggerWithColor logger)
public static void PrintAllDependencyGraphs(Dictionary<string, List<DependencyNode>?> dependencyGraphPerFramework, string targetPackage, IAnsiConsole logger)
{
// print empty line
logger.LogMinimal("");
logger.WriteLine();

// deduplicate the dependency graphs
List<List<string>> deduplicatedFrameworks = GetDeduplicatedFrameworks(dependencyGraphPerFramework);
Expand All @@ -51,71 +46,67 @@ public static void PrintAllDependencyGraphs(Dictionary<string, List<DependencyNo
/// <param name="topLevelNodes">The top-level package nodes of the dependency graph.</param>
/// <param name="targetPackage">The package we want the dependency paths for.</param>
/// <param name="logger"></param>
private static void PrintDependencyGraphPerFramework(List<string> frameworks, List<DependencyNode>? topLevelNodes, string targetPackage, ILoggerWithColor logger)
private static void PrintDependencyGraphPerFramework(List<string> frameworks, List<DependencyNode>? topLevelNodes, string targetPackage, IAnsiConsole logger)
{
// print framework header
foreach (var framework in frameworks)
{
logger.LogMinimal($" [{framework}]");
}

logger.LogMinimal($" {ChildPrefixSymbol}");
var tree = new Tree(string.Join("\n", frameworks.Select(f => $"[[{f}]]")));

if (topLevelNodes == null || topLevelNodes.Count == 0)
{
logger.LogMinimal($" {LastChildNodeSymbol}{Strings.WhyCommand_Message_NoDependencyGraphsFoundForFramework}\n\n");
tree.AddNode(Strings.WhyCommand_Message_NoDependencyGraphsFoundForFramework);
logger.Write(PadTree(tree));
return;
}

var stack = new Stack<StackOutputData>();

// initialize the stack with all top-level nodes
int counter = 0;
foreach (var node in topLevelNodes.OrderByDescending(c => c.Id, StringComparer.OrdinalIgnoreCase))
{
stack.Push(new StackOutputData(node, prefix: " ", isLastChild: counter++ == 0));
stack.Push(new StackOutputData
{
Node = node,
ParentNode = tree
});
}

// print the dependency graph
while (stack.Count > 0)
{
var current = stack.Pop();

string currentPrefix, childPrefix;
if (current.IsLastChild)
{
currentPrefix = current.Prefix + LastChildNodeSymbol;
childPrefix = current.Prefix + LastChildPrefixSymbol;
}
else
{
currentPrefix = current.Prefix + ChildNodeSymbol;
childPrefix = current.Prefix + ChildPrefixSymbol;
}

// print current node
if (current.Node.Id.Equals(targetPackage, StringComparison.OrdinalIgnoreCase))
{
logger.LogMinimal($"{currentPrefix}", Console.ForegroundColor);
logger.LogMinimal($"{current.Node.Id} (v{current.Node.Version})\n", TargetPackageColor);
}
else
{
logger.LogMinimal($"{currentPrefix}{current.Node.Id} (v{current.Node.Version})");
}
var treeNodeText = GetNodeText(current.Node, targetPackage);
var treeNode = current.ParentNode.AddNode(treeNodeText);

if (current.Node.Children?.Count > 0)
{
// push all the node's children onto the stack
counter = 0;
foreach (var child in current.Node.Children.OrderByDescending(c => c.Id, StringComparer.OrdinalIgnoreCase))
{
stack.Push(new StackOutputData(child, childPrefix, isLastChild: counter++ == 0));
stack.Push(new StackOutputData
{
Node = child,
ParentNode = treeNode
});
}
}
}

logger.LogMinimal("");
logger.Write(PadTree(tree));
logger.WriteLine();
}

private static IRenderable GetNodeText(DependencyNode node, string targetPackage)
{
string text = $"{node.Id} (v{node.Version})";
Style? style = node.Id.Equals(targetPackage, StringComparison.OrdinalIgnoreCase)
? new Style(foreground: TargetPackageColor)
: null;

return new Text(text, style);
}

private static IRenderable PadTree(Tree tree)
{
return new Padder(tree, new Padding(left: 2, 0, 0, 0));
}

/// <summary>
Expand Down Expand Up @@ -173,16 +164,9 @@ private static int GetDependencyGraphHashCode(List<DependencyNode>? graph)

private class StackOutputData
{
public DependencyNode Node { get; set; }
public string Prefix { get; set; }
public bool IsLastChild { get; set; }
public required DependencyNode Node { get; init; }

public StackOutputData(DependencyNode node, string prefix, bool isLastChild)
{
Node = node;
Prefix = prefix;
IsLastChild = isLastChild;
}
public required IHasTreeNodes ParentNode { get; init; }
}
}
}
23 changes: 12 additions & 11 deletions src/NuGet.Core/NuGet.CommandLine.XPlat/Commands/Why/WhyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.CommandLineUtils;
using Spectre.Console;

namespace NuGet.CommandLine.XPlat.Commands.Why
{
Expand All @@ -22,9 +23,9 @@ internal static void Register(CommandLineApplication app)
});
}

internal static void Register(Command rootCommand, Func<ILoggerWithColor> getLogger)
internal static void Register(Command rootCommand, IAnsiConsole console)
{
Register(rootCommand, getLogger, WhyCommandRunner.ExecuteCommand);
Register(rootCommand, console, WhyCommandRunner.ExecuteCommand);
}

/// <summary>
Expand All @@ -34,10 +35,12 @@ internal static void Register(Command rootCommand, Func<ILoggerWithColor> getLog
/// <param name="rootCommand">The <c>dotnet nuget</c> command handler, to add <c>why</c> to.</param>
public static void GetWhyCommand(Command rootCommand)
{
Register(rootCommand, CommandOutputLogger.Create, WhyCommandRunner.ExecuteCommand);
Register(rootCommand,
Spectre.Console.AnsiConsole.Console,
WhyCommandRunner.ExecuteCommand);
}

internal static void Register(Command rootCommand, Func<ILoggerWithColor> getLogger, Func<WhyCommandArgs, Task<int>> action)
internal static void Register(Command rootCommand, IAnsiConsole console, Func<WhyCommandArgs, Task<int>> action)
{
var whyCommand = new DocumentedCommand("why", Strings.WhyCommand_Description, "https://aka.ms/dotnet/nuget/why");

Expand Down Expand Up @@ -100,23 +103,21 @@ bool HasPathArgument(ArgumentResult ar)

whyCommand.SetAction(async (parseResult, cancellationToken) =>
{
ILoggerWithColor logger = getLogger();

try
{
var whyCommandArgs = new WhyCommandArgs(
parseResult.GetValue(path),
parseResult.GetValue(package),
parseResult.GetValue(frameworks),
logger,
parseResult.GetValue(path)!,
parseResult.GetValue(package)!,
parseResult.GetValue(frameworks)!,
console,
cancellationToken);

int exitCode = await action(whyCommandArgs);
return exitCode;
}
catch (ArgumentException ex)
{
logger.LogError(ex.Message);
console.Markup($"[red]{ex.Message}[/]");
return ExitCodes.InvalidArguments;
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
using System;
using System.Collections.Generic;
using System.Threading;
using Spectre.Console;

namespace NuGet.CommandLine.XPlat.Commands.Why
{
Expand All @@ -14,7 +15,7 @@ internal class WhyCommandArgs
public string Path { get; }
public string Package { get; }
public List<string> Frameworks { get; }
public ILoggerWithColor Logger { get; }
public IAnsiConsole Logger { get; }
public CancellationToken CancellationToken { get; }

/// <summary>
Expand All @@ -29,7 +30,7 @@ public WhyCommandArgs(
string path,
string package,
List<string> frameworks,
ILoggerWithColor logger,
IAnsiConsole logger,
CancellationToken cancellationToken)
{
Path = path ?? throw new ArgumentNullException(nameof(path));
Expand Down
Loading