Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve package rendering in dotnet-nugetize #307

Merged
merged 1 commit into from
Feb 19, 2023
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
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