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
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
1 change: 1 addition & 0 deletions src/NuGetLicense/CommandLineOptionsParser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,7 @@ public IOutputFormatter GetOutputFormatter(OutputType outputType, bool returnErr
OutputType.JsonPretty => new Output.Json.JsonOutputFormatter(true, returnErrorsOnly, !includeIgnoredPackages),
OutputType.Table => new Output.Table.TableOutputFormatter(returnErrorsOnly, !includeIgnoredPackages),
OutputType.Markdown => new Output.Table.TableOutputFormatter(returnErrorsOnly, !includeIgnoredPackages, printMarkdown: true),
OutputType.Csv => new Output.Csv.CsvOutputFormatter(returnErrorsOnly, !includeIgnoredPackages),
_ => throw new ArgumentOutOfRangeException($"{outputType} not supported")
};
}
Expand Down
81 changes: 81 additions & 0 deletions src/NuGetLicense/Output/Csv/CsvOutputFormatter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

using System.Text;
using NuGetLicense.LicenseValidator;

namespace NuGetLicense.Output.Csv
{
public class CsvOutputFormatter : IOutputFormatter
{
private readonly bool _printErrorsOnly;
private readonly bool _skipIgnoredPackages;

public CsvOutputFormatter(bool printErrorsOnly, bool skipIgnoredPackages)
{
_printErrorsOnly = printErrorsOnly;
_skipIgnoredPackages = skipIgnoredPackages;
}

public async Task Write(Stream stream, IList<LicenseValidationResult> results)
{
if (_printErrorsOnly)
{
results = results.Where(r => r.ValidationErrors.Any()).ToList();
}

if (_skipIgnoredPackages)
{
results = results
.Where(r => r.LicenseInformationOrigin != LicenseInformationOrigin.Ignored)
.ToList();
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

using var writer = new StreamWriter(stream, new UTF8Encoding(false), bufferSize: 1024, leaveOpen: true);

await writer.WriteLineAsync(
"Package,Version,License Information Origin,License,License Url,Copyright,Authors,Package Project Url,Errors with Context");

foreach (LicenseValidationResult license in results)
{
string[] row = new[]
{
EscapeCsvValue(license.PackageId), EscapeCsvValue(license.PackageVersion.ToString()),
EscapeCsvValue(license.LicenseInformationOrigin.ToString()),
EscapeCsvValue(license.License ?? string.Empty),
EscapeCsvValue(license.LicenseUrl ?? string.Empty),
EscapeCsvValue(license.Copyright ?? string.Empty),
EscapeCsvValue(license.Authors ?? string.Empty),
EscapeCsvValue(license.PackageProjectUrl ?? string.Empty),
GetValidationErrorsString(license.ValidationErrors)
};

await writer.WriteLineAsync(string.Join(",", row));
}

await writer.FlushAsync();
}

private static string EscapeCsvValue(string? value)
{
if (string.IsNullOrEmpty(value))
{
return string.Empty;
}

if (value.Contains(',') || value.Contains('"') || value.Contains('\n') || value.Contains('\r'))
{
string escaped = value!.Replace("\"", "\"\"");
return $"\"{escaped}\"";
}

return value!;
}

private static string GetValidationErrorsString(IEnumerable<ValidationError> errors)
{
string result = string.Join("; ", errors.Select(e => $"{e.Error} ({e.Context})"));
return EscapeCsvValue(result);
}
}
}
2 changes: 1 addition & 1 deletion src/NuGetLicense/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public class Program : ICommandLineOptions
[Option("-d|--license-information-download-location", Description = "Specifies a folder where the application will download all licenses provided via license URLs.")]
public string? DownloadLicenseInformation { get; set; }

[Option("-o|--output", Description = "Specifies the output format. Valid values are Table, Markdown, Json, or JsonPretty (default: Table).")]
[Option("-o|--output", Description = "Specifies the output format. Valid values are Table, Markdown, Json, JsonPretty or CSV (default: Table).")]
public OutputType OutputType { get; set; } = OutputType.Table;

[Option("-err|--error-only", Description = "When set, only validation errors are returned as result. Otherwise, all validation results are always returned.")]
Expand Down
3 changes: 2 additions & 1 deletion src/NuGetUtility/OutputType.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public enum OutputType
Table,
Json,
JsonPretty,
Markdown
Markdown,
Csv
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ public async Task ValidateAsync_WithExceptionInOutputFormatter_ReturnsMinusOne()

LicenseOutput.IOutputFormatter throwingFormatter = Substitute.For<LicenseOutput.IOutputFormatter>();
throwingFormatter.Write(Arg.Any<Stream>(), Arg.Any<IList<LicenseValidationResult>>())
.Returns<Task>(_ => throw new InvalidOperationException("Test exception"));
.Returns(_ => throw new InvalidOperationException("Test exception"));
_optionsParser.GetOutputFormatter(OutputType.Table, false, false).Returns(throwingFormatter);

// Act
Expand Down
269 changes: 269 additions & 0 deletions tests/NuGetLicense.Test/Output/Csv/CsvOutputFormatterSpecialCases.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
// Licensed to the projects contributors.
// The license conditions are provided in the LICENSE file located in the project root

using System.Text;
using NuGetLicense.LicenseValidator;
using NuGetLicense.Output.Csv;
using HelperNuGetVersion = NuGetLicense.Test.Output.Helper.NuGetVersion;

namespace NuGetLicense.Test.Output.Csv
{
[TestFixture]
public class CsvOutputFormatterSpecialCases
{
private static readonly string s_newLine = Environment.NewLine;

[Test]
public async Task Should_EscapeCsv_WithSpecialCharacters_Correctly()
{
var licenses = new List<LicenseValidationResult>
{
new(
PackageId: "PackageId,With,Commas",
PackageVersion: new HelperNuGetVersion("1.0.0"),
PackageProjectUrl: null,
License: "MIT License",
LicenseUrl: null,
"Copyright \"2024\"",
"Author1, Author2",
Description: null,
Summary: null,
LicenseInformationOrigin.Expression,
ValidationErrors: new List<ValidationError>()
),
new(
PackageId: "PackageIdWith\nNewline",
PackageVersion: new HelperNuGetVersion("2.0.0"),
PackageProjectUrl: null,
License: "Apache-2.0",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Expression,
ValidationErrors: new List<ValidationError>()
)
};

string expected =
$"Package,Version,License Information Origin,License,License Url,Copyright,Authors,Package Project Url,Errors with Context{s_newLine}" +
$"\"PackageId,With,Commas\",1.0.0,Expression,MIT License,,\"Copyright \"\"2024\"\"\",\"Author1, Author2\",,{s_newLine}" +
$"\"PackageIdWith\nNewline\",2.0.0,Expression,Apache-2.0,,,,,{s_newLine}";

var csvFormatter = new CsvOutputFormatter(false, false);
using var memoryStream = new MemoryStream();

await csvFormatter.Write(memoryStream, licenses);

string result = Encoding.UTF8.GetString(memoryStream.ToArray());

Assert.That(result, Is.EqualTo(expected));
}

[Test]
public async Task Should_EscapeCsv_WithErrors_Correctly()
{
var licenses = new List<LicenseValidationResult>
{
new(
PackageId: "TestPackage",
PackageVersion: new HelperNuGetVersion("1.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Expression,
ValidationErrors: new List<ValidationError>
{
new("License not allowed", "MIT is not in the allowed list"),
new("Missing copyright", "No copyright information")
}
)
};

string expected =
$"Package,Version,License Information Origin,License,License Url,Copyright,Authors,Package Project Url,Errors with Context{s_newLine}" +
$"TestPackage,1.0.0,Expression,MIT,,,,,License not allowed (MIT is not in the allowed list); Missing copyright (No copyright information){s_newLine}";

var csvFormatter = new CsvOutputFormatter(false, false);
using var memoryStream = new MemoryStream();

await csvFormatter.Write(memoryStream, licenses);

string result = Encoding.UTF8.GetString(memoryStream.ToArray());

Assert.That(result, Is.EqualTo(expected));
}

[Test]
public async Task Should_EscapeCsv_WithSkipIgnoredFilter_Correctly()
{
var licenses = new List<LicenseValidationResult>
{
new(
PackageId: "NormalPackage",
PackageVersion: new HelperNuGetVersion("1.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Expression,
ValidationErrors: new List<ValidationError>()
),
new(
PackageId: "IgnoredPackage",
PackageVersion: new HelperNuGetVersion("2.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Ignored,
ValidationErrors: new List<ValidationError>()
)
};

string expected =
$"Package,Version,License Information Origin,License,License Url,Copyright,Authors,Package Project Url,Errors with Context{s_newLine}" +
$"NormalPackage,1.0.0,Expression,MIT,,,,,{s_newLine}";

var csvFormatter = new CsvOutputFormatter(false, skipIgnoredPackages: true);
using var memoryStream = new MemoryStream();

await csvFormatter.Write(memoryStream, licenses);

string result = Encoding.UTF8.GetString(memoryStream.ToArray());

Assert.That(result, Is.EqualTo(expected));
}

[Test]
public async Task Should_EscapeCsvCorrectly_IfPrintErrorsOnly()
{
var licenses = new List<LicenseValidationResult>
{
new(
PackageId: "Package1",
PackageVersion: new HelperNuGetVersion("1.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Expression
),
new(
PackageId: "Package2",
PackageVersion: new HelperNuGetVersion("2.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Expression
)
};

string expected =
$"Package,Version,License Information Origin,License,License Url,Copyright,Authors,Package Project Url,Errors with Context{s_newLine}";

var csvFormatter = new CsvOutputFormatter(printErrorsOnly: true, false);
using var memoryStream = new MemoryStream();

await csvFormatter.Write(memoryStream, licenses);

string result = Encoding.UTF8.GetString(memoryStream.ToArray());
Assert.That(result, Is.EqualTo(expected));
}

[Test]
public async Task Should_ApplyBothFilters_WhenSpecialOptionsAreTrue()
{
var licenses = new List<LicenseValidationResult>
{
new(
PackageId: "Package1",
PackageVersion: new HelperNuGetVersion("1.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Expression,
ValidationErrors: new List<ValidationError>()
),
new(
PackageId: "Package2",
PackageVersion: new HelperNuGetVersion("2.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Ignored,
ValidationErrors: new List<ValidationError> { new("Test error", "Context") }
),
new(
PackageId: "Package3", // should contain because _printErrorsOnly = true & _skipIgnoredPackages = true
PackageVersion: new HelperNuGetVersion("3.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Expression,
ValidationErrors: new List<ValidationError> { new("Test error", "Context") }
),
new(
PackageId: "Package4",
PackageVersion: new HelperNuGetVersion("4.0.0"),
PackageProjectUrl: null,
License: "MIT",
LicenseUrl: null,
Copyright: null,
Authors: null,
Description: null,
Summary: null,
LicenseInformationOrigin.Ignored,
ValidationErrors: new List<ValidationError>()
)
};

string expected =
$"Package,Version,License Information Origin,License,License Url,Copyright,Authors,Package Project Url,Errors with Context{s_newLine}" +
$"Package3,3.0.0,Expression,MIT,,,,,Test error (Context){s_newLine}";

var csvFormatter = new CsvOutputFormatter(printErrorsOnly: true, skipIgnoredPackages: true);
using var memoryStream = new MemoryStream();

await csvFormatter.Write(memoryStream, licenses);

string result = Encoding.UTF8.GetString(memoryStream.ToArray());

Assert.That(result, Does.Contain("Package3"));
Assert.That(result, Does.Not.Contain("Package1"));
Assert.That(result, Does.Not.Contain("Package2"));
Assert.That(result, Does.Not.Contain("Package4"));
Assert.That(result, Is.EqualTo(expected));
}
}
}
Loading
Loading