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

Add an ignored columns option #103

Open
wants to merge 11 commits into
base: main
Choose a base branch
from
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ Usage: nuget-license [options]
| `-include-ignored\|--include-ignored-packages` | If this option is set, the packages that are ignored from validation are still included in the output. |
| `-exclude-projects\|--exclude-projects-matching <EXCLUDED_PROJECTS>` | This option allows to specify project name(s) to exclude from the analysis. This can be useful to exclude test projects from the analysis when supplying a solution file as input. Wildcard characters (*) are supported to specify ranges of ignored projects. The input can either be a file name containing a list of project names in json format or a plain string that is then used as a single entry. |
| `-f\|--target-framework <TARGET_FRAMEWORK>` | This option allows to select a Target framework moniker (https://learn.microsoft.com/en-us/dotnet/standard/frameworks) for which to analyze dependencies. |
| `-ignored-columns\|--ignored-columns-from-output <column names>` | This option allows to remove columns from the table output. |
| `-?\|-h\|--help` | Show help information. |

## Example tool commands
Expand Down
32 changes: 32 additions & 0 deletions src/NuGetUtility/Extensions/EnumExtension.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

using System.ComponentModel;
using System.Reflection;

namespace NuGetUtility.Extensions
{
public static class EnumExtension
{
public static string? GetDescription(this Enum value)
{
Type type = value.GetType();
if (Enum.GetName(type, value) is not string name)
{
return null;
}

if (type.GetField(name) is not FieldInfo field)
{
return null;
}

if (Attribute.GetCustomAttribute(field, typeof(DescriptionAttribute)) is DescriptionAttribute attr)
{
return attr.Description;
}
return null;
}

}
}
56 changes: 54 additions & 2 deletions src/NuGetUtility/Output/Json/JsonOutputFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using System.Text.Json;
using NuGetUtility.LicenseValidator;
using NuGetUtility.Serialization;
Expand All @@ -12,7 +15,10 @@ public class JsonOutputFormatter : IOutputFormatter
private readonly bool _printErrorsOnly;
private readonly bool _skipIgnoredPackages;
private readonly JsonSerializerOptions _options;
public JsonOutputFormatter(bool prettyPrint, bool printErrorsOnly, bool skipIgnoredPackages)
private readonly HashSet<OutputColumnType>? _ignoredColumns;


public JsonOutputFormatter(bool prettyPrint, bool printErrorsOnly, bool skipIgnoredPackages, IEnumerable<OutputColumnType>? ignoredColumns = null)
{
_printErrorsOnly = printErrorsOnly;
_skipIgnoredPackages = skipIgnoredPackages;
Expand All @@ -21,6 +27,7 @@ public JsonOutputFormatter(bool prettyPrint, bool printErrorsOnly, bool skipIgno
Converters = { new NuGetVersionJsonConverter(), new ValidatedLicenseJsonConverterWithOmittingEmptyErrorList() },
WriteIndented = prettyPrint
};
_ignoredColumns = ignoredColumns?.ToHashSet();
}

public async Task Write(Stream stream, IList<LicenseValidationResult> results)
Expand All @@ -34,7 +41,52 @@ public async Task Write(Stream stream, IList<LicenseValidationResult> results)
results = results.Where(r => r.LicenseInformationOrigin != LicenseInformationOrigin.Ignored).ToList();
}

await JsonSerializer.SerializeAsync(stream, results, _options);
var resultType = typeof(LicenseValidationResult);
var props = resultType.GetProperties();
Dictionary<OutputColumnType, PropertyInfo> validColumns = new();

// Parse LicenseValidationResult properties to store the non-ignored columns
foreach (var field in props)
{
if (!Enum.TryParse(field.Name, out OutputColumnType colType))
{
continue;
}

if (_ignoredColumns?.Contains(colType) ?? false)
{
continue;
}

validColumns.Add(colType, field);
}

// Create the new array of dictionaries with only the non-ignored columns
var dictionaries = results.Select(result =>
{
var dictionary = new Dictionary<string, object>();


foreach (var field in validColumns)
{
object? value = field.Value.GetValue(result);

switch (value)
{
case null:
case IList { Count: 0 }:
continue;
default:
dictionary.Add(field.Value.Name, value);
break;
}
}

return dictionary;
});


await JsonSerializer.SerializeAsync(stream, dictionaries, _options);
}
}
}
37 changes: 25 additions & 12 deletions src/NuGetUtility/Output/Table/TableOutputFormatter.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

using NuGetUtility.Extensions;
using NuGetUtility.LicenseValidator;

namespace NuGetUtility.Output.Table
Expand All @@ -9,28 +10,30 @@ public class TableOutputFormatter : IOutputFormatter
{
private readonly bool _printErrorsOnly;
private readonly bool _skipIgnoredPackages;
private readonly HashSet<OutputColumnType>? _ignoredColumns;

public TableOutputFormatter(bool printErrorsOnly, bool skipIgnoredPackages)
public TableOutputFormatter(bool printErrorsOnly, bool skipIgnoredPackages, IEnumerable<OutputColumnType>? ignoredColumns = null)
{
_printErrorsOnly = printErrorsOnly;
_skipIgnoredPackages = skipIgnoredPackages;
_ignoredColumns = ignoredColumns?.ToHashSet();
}

public async Task Write(Stream stream, IList<LicenseValidationResult> results)
{
var errorColumnDefinition = new ColumnDefinition("Error", license => license.ValidationErrors.Select(e => e.Error), license => license.ValidationErrors.Any());
var errorColumnDefinition = new ColumnDefinition(OutputColumnType.ValidationErrors, license => license.ValidationErrors.Select(e => e.Error), license => license.ValidationErrors.Any());
ColumnDefinition[] columnDefinitions = new[]
{
new ColumnDefinition("Package", license => license.PackageId, license => true, true),
new ColumnDefinition("Version", license => license.PackageVersion, license => true, true),
new ColumnDefinition("License Information Origin", license => license.LicenseInformationOrigin, license => true, true),
new ColumnDefinition("License Expression", license => license.License, license => license.License != null),
new ColumnDefinition("License Url", license => license.LicenseUrl, license => license.LicenseUrl != null),
new ColumnDefinition("Copyright", license => license.Copyright, license => license.Copyright != null),
new ColumnDefinition("Authors", license => license.Authors, license => license.Authors != null),
new ColumnDefinition("Package Project Url",license => license.PackageProjectUrl, license => license.PackageProjectUrl != null),
new ColumnDefinition(OutputColumnType.PackageId, license => license.PackageId, license => true, true),
new ColumnDefinition(OutputColumnType.PackageVersion, license => license.PackageVersion, license => true, true),
new ColumnDefinition(OutputColumnType.LicenseInformationOrigin, license => license.LicenseInformationOrigin, license => true, true),
new ColumnDefinition(OutputColumnType.License, license => license.License, license => license.License != null),
new ColumnDefinition(OutputColumnType.LicenseUrl, license => license.LicenseUrl, license => license.LicenseUrl != null),
new ColumnDefinition(OutputColumnType.Copyright, license => license.Copyright, license => license.Copyright != null),
new ColumnDefinition(OutputColumnType.Authors, license => license.Authors, license => license.Authors != null),
new ColumnDefinition(OutputColumnType.PackageProjectUrl,license => license.PackageProjectUrl, license => license.PackageProjectUrl != null),
errorColumnDefinition,
new ColumnDefinition("Error Context", license => license.ValidationErrors.Select(e => e.Context), license => license.ValidationErrors.Any()),
new ColumnDefinition(OutputColumnType.ErrorContext, license => license.ValidationErrors.Select(e => e.Context), license => license.ValidationErrors.Any()),
};

foreach (LicenseValidationResult license in results)
Expand All @@ -41,6 +44,14 @@ public async Task Write(Stream stream, IList<LicenseValidationResult> results)
}
}

if (_ignoredColumns is not null)
{
foreach (ColumnDefinition? definition in columnDefinitions)
{
definition.Enabled &= !_ignoredColumns.Contains(definition.Type);
}
}

if (_printErrorsOnly)
{
results = results.Where(r => r.ValidationErrors.Any()).ToList();
Expand All @@ -59,9 +70,11 @@ await TablePrinterExtensions
.Print();
}

private sealed record ColumnDefinition(string Title, Func<LicenseValidationResult, object?> PropertyAccessor, Func<LicenseValidationResult, bool> IsRelevant, bool Enabled = false)
private sealed record ColumnDefinition(OutputColumnType Type, Func<LicenseValidationResult, object?> PropertyAccessor, Func<LicenseValidationResult, bool> IsRelevant, bool Enabled = false)
{
public bool Enabled { get; set; } = Enabled;

public string Title { get; } = Type.GetDescription() ?? throw new InvalidOperationException($"Enum value {Type} is missing the Description attribute");
}
}
}
5 changes: 4 additions & 1 deletion src/NuGetUtility/Output/Table/TablePrinter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ public class TablePrinter

public TablePrinter(Stream stream, IEnumerable<string> titles)
{
_stream = stream;
_titles = titles.ToArray();
_stream = stream;
_lengths = _titles.Select(t => t.Length).ToArray();

if (_titles.Length == 0)
throw new InvalidOperationException("Too many columns ignored would result in a empty file !");
}

public void AddRow(object?[] row)
Expand Down
30 changes: 30 additions & 0 deletions src/NuGetUtility/OutputColumnType.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

using System.ComponentModel;

namespace NuGetUtility;

public enum OutputColumnType
{
[Description("Package")]
PackageId,
[Description("Version")]
PackageVersion,
[Description("License Information Origin")]
LicenseInformationOrigin,
[Description("License Expression")]
License, // License Expression
[Description("License Url")]
LicenseUrl,
[Description("Copyright")]
Copyright,
[Description("Authors")]
Authors,
[Description("Package Project Url")]
PackageProjectUrl,
[Description("Error")]
ValidationErrors,
[Description("Error Context")]
ErrorContext,
}
34 changes: 31 additions & 3 deletions src/NuGetUtility/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,11 @@ public class Program
Description = "This option allows to select a Target framework moniker (https://learn.microsoft.com/en-us/dotnet/standard/frameworks) for which to analyze dependencies.")]
public string? TargetFramework { get; } = null;

[Option(LongName = "ignored-columns-from-output",
ShortName = "ignored-columns",
Description = "This option allows to specify column name(s) to exclude from the output")]
public string? IgnoredColumns { get; } = null;

private static string GetVersion()
=> typeof(Program).Assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>()?.InformationalVersion ?? string.Empty;

Expand Down Expand Up @@ -180,9 +185,9 @@ private IOutputFormatter GetOutputFormatter()
{
return OutputType switch
{
OutputType.Json => new JsonOutputFormatter(false, ReturnErrorsOnly, !IncludeIgnoredPackages),
OutputType.JsonPretty => new JsonOutputFormatter(true, ReturnErrorsOnly, !IncludeIgnoredPackages),
OutputType.Table => new TableOutputFormatter(ReturnErrorsOnly, !IncludeIgnoredPackages),
OutputType.Json => new JsonOutputFormatter(false, ReturnErrorsOnly, !IncludeIgnoredPackages, GetIgnoredColumns()),
OutputType.JsonPretty => new JsonOutputFormatter(true, ReturnErrorsOnly, !IncludeIgnoredPackages, GetIgnoredColumns()),
OutputType.Table => new TableOutputFormatter(ReturnErrorsOnly, !IncludeIgnoredPackages, GetIgnoredColumns()),
_ => throw new ArgumentOutOfRangeException($"{OutputType} not supported")
};
}
Expand Down Expand Up @@ -286,5 +291,28 @@ private string[] GetInputFiles()

throw new FileNotFoundException("Please provide an input file");
}

private OutputColumnType[] GetIgnoredColumns()
{
if (IgnoredColumns == null)
{
return Array.Empty<OutputColumnType>();
}

if (File.Exists(IgnoredColumns))
{
string[] columnNames = JsonSerializer.Deserialize<string[]>(File.ReadAllText(IgnoredColumns))!;
try
{
return columnNames.Select(columnName => (OutputColumnType)Enum.Parse(typeof(OutputColumnType), columnName, true)).ToArray();
}
catch(ArgumentException e)
{
throw new ArgumentOutOfRangeException($"One of the column names ({e.ParamName}) isn't valid");
}
}

return new[] { (OutputColumnType)Enum.Parse(typeof(OutputColumnType), IgnoredColumns, true) };
}
}
}
Loading
Loading