Skip to content

Commit

Permalink
Improve package rendering in dotnet-nugetize
Browse files Browse the repository at this point in the history
Switch to Spectre.Console and a treeview.
  • Loading branch information
kzu committed Feb 19, 2023
1 parent d747b62 commit 320ee3d
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 92 deletions.
2 changes: 1 addition & 1 deletion src/Directory.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<BuildOnPack>false</BuildOnPack>

<GenerateDocumentationFile>false</GenerateDocumentationFile>
<Nullable>disable</Nullable>
<Nullable>annotations</Nullable>
<SignAssembly>false</SignAssembly>

<!-- Ignore warning for: Package 'NuGet.ProjectManagement 4.2.0' was restored using '.NETFramework,Version=v4.6.1, -->
Expand Down
184 changes: 94 additions & 90 deletions src/dotnet-nugetize/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
using System.Reflection;
using System.Text;
using System.Xml.Linq;
using ColoredConsole;
using Mono.Options;
using Spectre.Console;

namespace NuGetize
{
class Program
{
static readonly Style yellow = new(Color.Yellow);
static readonly Style errorStyle = new(Color.Red);
static readonly Style warningStyle = yellow;

bool binlog = Debugger.IsAttached;
bool debug = Debugger.IsAttached && Environment.GetEnvironmentVariable("DEBUG_NUGETIZER") != "0";
bool quiet = false;
Expand Down Expand Up @@ -49,8 +53,8 @@ int Run(string[] args)

if (version)
{
Console.WriteLine($"{ThisAssembly.Project.Product} version {ThisAssembly.Project.Version}+{ThisAssembly.Project.RepositorySha}");
Console.WriteLine($"{ThisAssembly.Project.Copyright}");
AnsiConsole.WriteLine($"{ThisAssembly.Project.Product} version {ThisAssembly.Project.Version}+{ThisAssembly.Project.RepositorySha}");
AnsiConsole.WriteLine($"{ThisAssembly.Project.Copyright}");
}
else
{
Expand Down Expand Up @@ -84,7 +88,7 @@ int Run(string[] args)

if (help)
{
Console.WriteLine($"Usage: {ThisAssembly.Project.ToolCommandName} [options] [msbuild args]");
AnsiConsole.WriteLine($"Usage: {ThisAssembly.Project.ToolCommandName} [options] [msbuild args]");
options.WriteOptionDescriptions(Console.Out);
return 0;
}
Expand Down Expand Up @@ -135,7 +139,7 @@ int Execute()
{
// The error might have been caused by us not being able to write our slnTargets
if (project.EndsWith(".sln") && !deleteSlnTargets)
ColorConsole.WriteLine($"Solution targets '{slnTargets}' already exists. NuGetizing all projects in the solution is therefore required.".Yellow());
AnsiConsole.Write(new Paragraph($"Solution targets '{slnTargets}' already exists. NuGetizing all projects in the solution is therefore required.", warningStyle));

if (binlog)
TryOpenBinlog();
Expand All @@ -150,7 +154,7 @@ int Execute()
{
// The error might have been caused by us not being able to write our slnTargets
if (project.EndsWith(".sln") && !deleteSlnTargets)
ColorConsole.WriteLine($"Solution targets '{slnTargets}' already exists. NuGetizing all projects in the solution is therefore required.".Yellow());
AnsiConsole.Write(new Paragraph($"Solution targets '{slnTargets}' already exists. NuGetizing all projects in the solution is therefore required.", warningStyle));

if (binlog)
TryOpenBinlog();
Expand All @@ -169,18 +173,21 @@ int Execute()

if (!File.Exists(file))
{
ColorConsole.WriteLine("Failed to discover nugetized content.".Red());
AnsiConsole.Write(new Paragraph("Failed to discover nugetized content.", errorStyle));
return -1;
}

var doc = XDocument.Load(file);
Tree? root = default;

if (contentsOnly)
{
root = new Tree("[yellow]Package Contents[/]");

if (project.EndsWith(".sln"))
ColorConsole.WriteLine($"Solution {Path.GetFileName(project)} contains only non-packable project(s), rendering contributed package contents.".Yellow());
AnsiConsole.Write(new Paragraph($"Solution {Path.GetFileName(project)} contains only non-packable project(s), rendering contributed package contents.", errorStyle));
else
ColorConsole.WriteLine($"Project {Path.GetFileName(project)} is not packable, rendering its contributed package contents.".Yellow());
AnsiConsole.Write(new Paragraph($"Project {Path.GetFileName(project)} is not packable, rendering its contributed package contents.", warningStyle));

var dependencies = doc.Root.Descendants("PackageContent")
.Where(x =>
Expand All @@ -193,29 +200,15 @@ int Execute()
.ThenBy(x => x.Attribute("Include").Value)
.ToList();

if (dependencies.Count > 0)
{
ColorConsole.WriteLine($" Dependencies:".Yellow());
foreach (var group in dependencies.GroupBy(x => x.Element("TargetFramework").Value))
{
ColorConsole.WriteLine(" ", group.Key.Green());
foreach (var dependency in group)
{
ColorConsole.WriteLine(" ", dependency.Attribute("Include").Value.White(), $", {dependency.Element("Version").Value}".Gray());
}
}
}

ColorConsole.WriteLine($" Contents:".Yellow());
AddDependencies(root, dependencies);

var contents = doc.Root.Descendants("PackageContent")
.Where(x => x.Element("PackagePath") != null)
.Distinct(AnonymousComparer.Create<XElement>(x => x.Element("PackagePath").Value))
.OrderBy(x => Path.GetDirectoryName(x.Element("PackagePath").Value))
.ThenBy(x => x.Element("PackagePath").Value);

Render(contents.ToList(), 0, 0, "");
Console.WriteLine();
AddContents(root.AddNode("[yellow]Contents:[/]"), contents.ToList());
}
else
{
Expand All @@ -231,24 +224,36 @@ int Execute()
continue;

foundPackage = true;
ColorConsole.WriteLine($"Package: {Path.GetFileName(metadata.Element("NuPkg").Value)}".Yellow());
ColorConsole.WriteLine($" {metadata.Element("Nuspec").Value}".Yellow());

var grid = new Grid();
grid.AddColumn().AddColumn();
grid.AddRow(new Text("Package", yellow), new Grid().AddColumn()
.AddRow($"[yellow]{Path.GetFileName(metadata.Element("NuPkg").Value)}[/]")
.AddRow(new Text(metadata.Element("Nuspec").Value,
new Style(Color.Blue, decoration: Decoration.Underline, link: metadata.Element("Nuspec").Value))));

root = new Tree(grid);

var width = metadata.Elements()
.Select(x => x.Name.LocalName.Length)
.OrderByDescending(x => x)
.First();

var table = new Grid().AddColumn().AddColumn();
table.AddRow(new Text("Metadata:", yellow));

foreach (var md in metadata.Elements()
.Where(x =>
x.Name != "PackageId" &&
x.Name != "Nuspec" &&
x.Name != "NuPkg")
.OrderBy(x => x.Name.LocalName))
{
ColorConsole.WriteLine($" {md.Name.LocalName.PadRight(width)}: ", md.Value.White());
table.AddRow(new Text(md.Name.LocalName), new Text(md.Value));
}

root.AddNode(table);

var dependencies = doc.Root.Descendants("PackageContent")
.Where(x =>
"Dependency".Equals(x.Element("PackFolder")?.Value, StringComparison.OrdinalIgnoreCase) &&
Expand All @@ -261,20 +266,7 @@ int Execute()
.ThenBy(x => x.Attribute("Include").Value)
.ToList();

if (dependencies.Count > 0)
{
ColorConsole.WriteLine($" Dependencies:".Yellow());
foreach (var group in dependencies.GroupBy(x => x.Element("TargetFramework").Value))
{
ColorConsole.WriteLine(" ", group.Key.Green());
foreach (var dependency in group)
{
ColorConsole.WriteLine(" ", dependency.Attribute("Include").Value.White(), $", {dependency.Element("Version").Value}".Gray());
}
}
}

ColorConsole.WriteLine($" Contents:".Yellow());
AddDependencies(root, dependencies);

var contents = doc.Root.Descendants("PackageContent")
.Where(x =>
Expand All @@ -284,82 +276,94 @@ int Execute()
.OrderBy(x => Path.GetDirectoryName(x.Element("PackagePath").Value))
.ThenBy(x => x.Element("PackagePath").Value);

Render(contents.ToList(), 0, 0, "");
Console.WriteLine();
AddContents(root.AddNode("[yellow]Contents:[/]"), contents.ToList());
//Render(contents.ToList(), 0, 0, "");
//Console.WriteLine();
}

if (!foundPackage)
{
ColorConsole.WriteLine($"No package content was found.".Red());
AnsiConsole.Write(new Paragraph($"No package content was found.", errorStyle));
return -1;
}
}

Console.WriteLine();
if (root != null)
AnsiConsole.Write(root);

AnsiConsole.WriteLine();
if (items != null)
ColorConsole.WriteLine("> ", file.Yellow());
AnsiConsole.WriteLine($"> [yellow]{file}[/]");

if (binlog)
TryOpenBinlog();

return 0;
}

static int Render(IList<XElement> files, int index, int level, string path)
void AddContents(TreeNode node, List<XElement> contents)
{
var normalizedLevelPath = path == "" ? Path.DirectorySeparatorChar.ToString() : (Path.DirectorySeparatorChar + path + Path.DirectorySeparatorChar);
while (index < files.Count)
var parents = new Dictionary<string, TreeNode>();
parents.Add("", node);
foreach (var element in contents)
{
var element = files[index];
var file = element.Element("PackagePath").Value;
var dir = Path.GetDirectoryName(file);
var paths = dir.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }, StringSplitOptions.RemoveEmptyEntries);
var normalizeCurrentPath = Path.DirectorySeparatorChar + string.Join(Path.DirectorySeparatorChar, paths) + Path.DirectorySeparatorChar;

if (!normalizeCurrentPath.StartsWith(normalizedLevelPath) || paths.Length < level)
return index;

if (paths.Length > level)
var parent = node;
// Locate parent node given the relative path
if (paths.Length > 0)
{
ColorConsole.Write(new string(' ', (level + 1) * 2 + 2));
if (level == 0)
ColorConsole.Write("/".Gray());

ColorConsole.WriteLine(paths[level].Green(), "/".Gray());
index = Render(files, index, level + 1, string.Join(Path.DirectorySeparatorChar, paths[..(level + 1)]));
}
else
{
Console.Write(new string(' ', (level + 1) * 2 + 2));
if (level == 0)
ColorConsole.Write("/".Gray());

var attributes = new List<string>();
var packFolder = element.Element("PackFolder")?.Value;

if (packFolder != null &&
("content".Equals(packFolder, StringComparison.OrdinalIgnoreCase) ||
"contentFiles".Equals(packFolder, StringComparison.OrdinalIgnoreCase)))
for (var i = 0; i < paths.Length; i++)
{
if (element.Element("BuildAction")?.Value is string buildAction)
attributes.Add("buildAction=" + buildAction);
if (element.Element("CopyToOutput")?.Value is string copyToOutput)
attributes.Add("copyToOutput=" + copyToOutput);
if (element.Element("Flatten")?.Value is string flatten)
attributes.Add("flatten=" + flatten);
var key = string.Join('/', paths[0..(i + 1)]);
if (!parents.TryGetValue(key, out var existing))
{
parent = parent.AddNode($"[green]{paths[i]}[/]");
parents.Add(key, parent);
}
else
{
parent = existing;
}
}
}

ColorConsole.Write(Path.GetFileName(file).White());
if (attributes.Count > 0)
ColorConsole.Write((" (" + string.Join(',', attributes) + ")").Gray());
var attributes = new List<string>();
var packFolder = element.Element("PackFolder")?.Value;
if (packFolder != null &&
("content".Equals(packFolder, StringComparison.OrdinalIgnoreCase) ||
"contentFiles".Equals(packFolder, StringComparison.OrdinalIgnoreCase)))
{
if (element.Element("BuildAction")?.Value is string buildAction)
attributes.Add("buildAction=" + buildAction);
if (element.Element("CopyToOutput")?.Value is string copyToOutput)
attributes.Add("copyToOutput=" + copyToOutput);
if (element.Element("Flatten")?.Value is string flatten)
attributes.Add("flatten=" + flatten);
}

if (attributes.Count > 0)
parent.AddNode($"[white]{Path.GetFileName(file)}[/] [grey]({string.Join(',', attributes)})[/]");
else
parent.AddNode($"[white]{Path.GetFileName(file)}[/]");
}
}

Console.WriteLine();
void AddDependencies(Tree root, List<XElement> dependencies)
{
if (dependencies.Count == 0)
return;

index++;
var deps = root.AddNode("[yellow]Dependencies:[/]");
foreach (var group in dependencies.GroupBy(x => x.Element("TargetFramework").Value))
{
var tf = deps.AddNode($"[green]{group.Key}[/]");
foreach (var dependency in group)
{
tf.AddNode(Markup.FromInterpolated($"[white]{dependency.Attribute("Include").Value}[/], [grey]{dependency.Element("Version").Value}[/]"));
}
}

return index;
}

static void TryOpenBinlog()
Expand Down Expand Up @@ -389,7 +393,7 @@ bool Execute(string program, string arguments)
info.Environment["DEBUG_NUGETIZER"] = "1";

var proc = Process.Start(info);
proc.ErrorDataReceived += (sender, args) => ColorConsole.Write(args.Data.Red());
proc.ErrorDataReceived += (sender, args) => AnsiConsole.Write(new Paragraph(args.Data, errorStyle));

var output = new StringBuilder();
var timedout = false;
Expand Down
5 changes: 4 additions & 1 deletion src/dotnet-nugetize/dotnet-nugetize.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
<TargetFramework Condition="'$(BuildingInsideVisualStudio)' == 'true'">net6.0</TargetFramework>
<TargetFrameworks Condition="'$(BuildingInsideVisualStudio)' != 'true'">netcoreapp3.1;net6.0</TargetFrameworks>

<LangVersion>Latest</LangVersion>
<AppendTargetFrameworkToOutputPath>true</AppendTargetFrameworkToOutputPath>
<NuGetize>false</NuGetize>

Expand All @@ -17,10 +18,12 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="ColoredConsole" Version="1.0.0" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.1.1" PrivateAssets="all" />
<PackageReference Include="Mono.Options" Version="6.12.0.148" />
<PackageReference Include="Nullable" Version="1.3.1" PrivateAssets="all" />
<PackageReference Include="PolySharp" Version="1.12.1" PrivateAssets="all" />
<PackageReference Include="Spectre.Console" Version="0.46.0" />
<PackageReference Include="Spectre.Console.Analyzer" Version="0.46.0" PrivateAssets="all" />
<PackageReference Include="ThisAssembly.Project" Version="1.0.10" PrivateAssets="all" />
</ItemGroup>

Expand Down

0 comments on commit 320ee3d

Please sign in to comment.