Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feature: Nuget Author helpers (#1352)
* add nuget author helpers * license tweaks * Update PackageSourceExtensions.cs * suppress ca1848 for nuget forwarder for now * import maths max helper used in mvvm tracking
- Loading branch information
Showing
12 changed files
with
2,414 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using Whipstaff.Core.Entities; | ||
using Whipstaff.Runtime.Extensions; | ||
|
||
namespace Whipstaff.Nuget | ||
{ | ||
/// <summary> | ||
/// Represents a nuget author username. | ||
/// </summary> | ||
public sealed class AuthorUsernameAsStringModel : IEntityAsString | ||
{ | ||
/// <summary> | ||
/// Initializes a new instance of the <see cref="AuthorUsernameAsStringModel"/> class. | ||
/// </summary> | ||
/// <param name="value">Nuget username as a string.</param> | ||
public AuthorUsernameAsStringModel(string value) | ||
{ | ||
value.ThrowIfNullOrWhitespace(); | ||
if (!value.IsAsciiLettersOrNumbers()) | ||
{ | ||
throw new ArgumentException("Author name must be ASCII letters or numbers", nameof(value)); | ||
} | ||
|
||
Value = value; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public string Value { get; } | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using NuGet.Common; | ||
using NuGet.Configuration; | ||
using NuGet.Protocol; | ||
using NuGet.Protocol.Core.Types; | ||
|
||
namespace Whipstaff.Nuget | ||
{ | ||
/// <summary> | ||
/// Helpers for working with the NuGet client API. | ||
/// </summary> | ||
public static class NugetClientHelpers | ||
{ | ||
/// <summary> | ||
/// Gets a list of packages for the given author name. | ||
/// </summary> | ||
/// <param name="authorName">Username of the author.</param> | ||
/// <param name="nugetForwardingToNetCoreLogger">Forwarding logger instance.</param> | ||
/// <param name="cancellationToken">The cancellation token for the operation.</param> | ||
/// <returns>List of packages for the author.</returns> | ||
public static async Task<IList<IPackageSearchMetadata>> GetPackagesForAuthor( | ||
AuthorUsernameAsStringModel authorName, | ||
NugetForwardingToNetCoreLogger nugetForwardingToNetCoreLogger, | ||
CancellationToken cancellationToken) | ||
{ | ||
ArgumentNullException.ThrowIfNull(authorName); | ||
ArgumentNullException.ThrowIfNull(nugetForwardingToNetCoreLogger); | ||
|
||
var packageSourceProvider = new PackageSourceProvider(new Settings(Environment.CurrentDirectory)); | ||
var sources = packageSourceProvider.LoadPackageSources(); | ||
var packages = new List<IPackageSearchMetadata>(); | ||
|
||
foreach (var packageSource in sources) | ||
{ | ||
var packagesForAuthor = await packageSource.GetPackagesForAuthor( | ||
authorName, | ||
nugetForwardingToNetCoreLogger, | ||
cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
packages.AddRange(packagesForAuthor); | ||
} | ||
|
||
return packages; | ||
} | ||
|
||
/// <summary> | ||
/// Gets a list of selected information per package for the given author name. | ||
/// </summary> | ||
/// <typeparam name="TResult">Return type for the selected information.</typeparam> | ||
/// <param name="authorName">Username of the author.</param> | ||
/// <param name="selector">selection predicate of the data to return.</param> | ||
/// <param name="nugetForwardingToNetCoreLogger">Forwarding logger instance.</param> | ||
/// <param name="cancellationToken">The cancellation token for the operation.</param> | ||
/// <returns>List of selected output based on packages for the author.</returns> | ||
public static async Task<IList<TResult>> GetPackagesForAuthor<TResult>( | ||
AuthorUsernameAsStringModel authorName, | ||
Func<IPackageSearchMetadata, TResult> selector, | ||
NugetForwardingToNetCoreLogger nugetForwardingToNetCoreLogger, | ||
CancellationToken cancellationToken) | ||
{ | ||
ArgumentNullException.ThrowIfNull(selector); | ||
|
||
var packageSourceProvider = new PackageSourceProvider(new Settings(Environment.CurrentDirectory)); | ||
var sources = packageSourceProvider.LoadPackageSources(); | ||
var packages = new List<TResult>(); | ||
|
||
foreach (var packageSource in sources) | ||
{ | ||
var packagesForAuthor = await packageSource.GetPackagesForAuthor( | ||
authorName, | ||
selector, | ||
nugetForwardingToNetCoreLogger, | ||
cancellationToken) | ||
.ConfigureAwait(false); | ||
|
||
packages.AddRange(packagesForAuthor); | ||
} | ||
|
||
return packages; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.Logging; | ||
using NuGet.Common; | ||
|
||
namespace Whipstaff.Nuget | ||
{ | ||
/// <summary> | ||
/// Nuget logger that forwards to a .NET Core logger. | ||
/// </summary> | ||
/// <remarks> | ||
/// This is based upon https://raw.githubusercontent.com/NuGet/NuGet.Jobs/943076ea59f3bad50b019c27e511bf82808575aa/src/NuGet.Services.Metadata.Catalog.Monitoring/Utility/CommonLogger.cs | ||
/// Copyright (c) .NET Foundation. All rights reserved. | ||
/// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
/// </remarks> | ||
public sealed class NugetForwardingToNetCoreLogger : NuGet.Common.ILogger | ||
{ | ||
// This event ID is believed to be unused anywhere else but is otherwise arbitrary. | ||
private const int DefaultLogEventId = 23847; | ||
private static readonly EventId DefaultClientLogEvent = new EventId(DefaultLogEventId); | ||
private readonly Microsoft.Extensions.Logging.ILogger _internalLogger; | ||
|
||
/// <summary> | ||
/// Initializes a new instance of the <see cref="NugetForwardingToNetCoreLogger"/> class. | ||
/// </summary> | ||
/// <param name="logger">Net Core logging framework instance.</param> | ||
public NugetForwardingToNetCoreLogger(Microsoft.Extensions.Logging.ILogger logger) | ||
{ | ||
_internalLogger = logger ?? throw new ArgumentNullException(nameof(logger)); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void LogDebug(string data) | ||
{ | ||
#pragma warning disable CA1848 // Use the LoggerMessage delegates | ||
_internalLogger.LogDebug("{Data}", data); | ||
#pragma warning restore CA1848 // Use the LoggerMessage delegates | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void LogVerbose(string data) | ||
{ | ||
#pragma warning disable CA1848 // Use the LoggerMessage delegates | ||
_internalLogger.LogInformation("{Data}", data); | ||
#pragma warning restore CA1848 // Use the LoggerMessage delegates | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void LogInformation(string data) | ||
{ | ||
#pragma warning disable CA1848 // Use the LoggerMessage delegates | ||
_internalLogger.LogInformation("{Data}", data); | ||
#pragma warning restore CA1848 // Use the LoggerMessage delegates | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void LogMinimal(string data) | ||
{ | ||
#pragma warning disable CA1848 // Use the LoggerMessage delegates | ||
_internalLogger.LogInformation("{Data}", data); | ||
#pragma warning restore CA1848 // Use the LoggerMessage delegates | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void LogWarning(string data) | ||
{ | ||
#pragma warning disable CA1848 // Use the LoggerMessage delegates | ||
_internalLogger.LogWarning("{Data}", data); | ||
#pragma warning restore CA1848 // Use the LoggerMessage delegates | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void LogError(string data) | ||
{ | ||
#pragma warning disable CA1848 // Use the LoggerMessage delegates | ||
_internalLogger.LogError("{Data}", data); | ||
#pragma warning restore CA1848 // Use the LoggerMessage delegates | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void LogInformationSummary(string data) | ||
{ | ||
#pragma warning disable CA1848 // Use the LoggerMessage delegates | ||
_internalLogger.LogInformation("{Data}", data); | ||
#pragma warning restore CA1848 // Use the LoggerMessage delegates | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void Log(NuGet.Common.LogLevel level, string data) | ||
{ | ||
_internalLogger.Log(GetLogLevel(level), DefaultClientLogEvent, data, null, (str, ex) => str); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public Task LogAsync(NuGet.Common.LogLevel level, string data) | ||
{ | ||
_internalLogger.Log(GetLogLevel(level), DefaultClientLogEvent, data, null, (str, ex) => str); | ||
return Task.CompletedTask; | ||
} | ||
|
||
/// <inheritdoc/> | ||
public void Log(ILogMessage message) | ||
{ | ||
if (message == null) | ||
{ | ||
return; | ||
} | ||
|
||
_internalLogger.Log(GetLogLevel(message.Level), new EventId((int)message.Code), message.Message, null, (str, ex) => str); | ||
} | ||
|
||
/// <inheritdoc/> | ||
public Task LogAsync(ILogMessage message) | ||
{ | ||
if (message == null) | ||
{ | ||
return Task.CompletedTask; | ||
} | ||
|
||
_internalLogger.Log(GetLogLevel(message.Level), new EventId((int)message.Code), message.Message, null, (str, ex) => str); | ||
return Task.CompletedTask; | ||
} | ||
|
||
private static Microsoft.Extensions.Logging.LogLevel GetLogLevel(NuGet.Common.LogLevel logLevel) | ||
{ | ||
return logLevel switch | ||
{ | ||
NuGet.Common.LogLevel.Debug => Microsoft.Extensions.Logging.LogLevel.Debug, | ||
NuGet.Common.LogLevel.Verbose => Microsoft.Extensions.Logging.LogLevel.Information, | ||
NuGet.Common.LogLevel.Information => Microsoft.Extensions.Logging.LogLevel.Information, | ||
NuGet.Common.LogLevel.Minimal => Microsoft.Extensions.Logging.LogLevel.Information, | ||
NuGet.Common.LogLevel.Warning => Microsoft.Extensions.Logging.LogLevel.Warning, | ||
NuGet.Common.LogLevel.Error => Microsoft.Extensions.Logging.LogLevel.Error, | ||
_ => Microsoft.Extensions.Logging.LogLevel.None, | ||
}; | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
// Copyright (c) 2022 DHGMS Solutions and Contributors. All rights reserved. | ||
// This file is licensed to you under the MIT license. | ||
// See the LICENSE file in the project root for full license information. | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Threading; | ||
using System.Threading.Tasks; | ||
using NuGet.Common; | ||
using NuGet.Protocol.Core.Types; | ||
|
||
namespace Whipstaff.Nuget | ||
{ | ||
/// <summary> | ||
/// Extension methods for <see cref="PackageSearchResource" />. | ||
/// </summary> | ||
public static class PackageSearchResourceExtensions | ||
{ | ||
/// <summary> | ||
/// Gets a list of packages for the given author name in the specific package search resource. | ||
/// </summary> | ||
/// <param name="packageSearchResource">Package search resource to check.</param> | ||
/// <param name="authorName">Username of the author.</param> | ||
/// <param name="logger">NuGet logging framework instance.</param> | ||
/// <param name="cancellationToken">The cancellation token for the operation.</param> | ||
/// <returns>List of packages for the author.</returns> | ||
public static async Task<IList<IPackageSearchMetadata>> GetPackagesForAuthor( | ||
this PackageSearchResource packageSearchResource, | ||
AuthorUsernameAsStringModel authorName, | ||
ILogger logger, | ||
CancellationToken cancellationToken) | ||
{ | ||
ArgumentNullException.ThrowIfNull(packageSearchResource); | ||
ArgumentNullException.ThrowIfNull(authorName); | ||
|
||
var result = new List<IPackageSearchMetadata>(); | ||
var searchTerm = $"author:{authorName.Value}"; | ||
var searchFilter = new SearchFilter(false); | ||
|
||
var packageCount = 0; | ||
var skip = 0; | ||
|
||
do | ||
{ | ||
var searchResult = await packageSearchResource.SearchAsync( | ||
searchTerm, | ||
searchFilter, | ||
skip: skip, | ||
take: 1000, | ||
logger, | ||
cancellationToken).ConfigureAwait(false); | ||
|
||
var packages = searchResult.ToList(); | ||
packageCount = packages.Count; | ||
|
||
result.AddRange(packages); | ||
skip += 1000; | ||
} | ||
while (packageCount == 1000); | ||
|
||
return result; | ||
} | ||
|
||
/// <summary> | ||
/// Gets a list of selected information per package for the given author name in the specific package search resource. | ||
/// </summary> | ||
/// <typeparam name="TResult">Return type for the selected information.</typeparam> | ||
/// <param name="packageSearchResource">Package search resource to check.</param> | ||
/// <param name="authorName">Username of the author.</param> | ||
/// <param name="selector">selection predicate of the data to return.</param> | ||
/// <param name="logger">NuGet logging framework instance.</param> | ||
/// <param name="cancellationToken">The cancellation token for the operation.</param> | ||
/// <returns>List of packages for the author.</returns> | ||
public static async Task<IList<TResult>> GetPackagesForAuthor<TResult>( | ||
this PackageSearchResource packageSearchResource, | ||
AuthorUsernameAsStringModel authorName, | ||
Func<IPackageSearchMetadata, TResult> selector, | ||
ILogger logger, | ||
CancellationToken cancellationToken) | ||
{ | ||
ArgumentNullException.ThrowIfNull(packageSearchResource); | ||
ArgumentNullException.ThrowIfNull(authorName); | ||
ArgumentNullException.ThrowIfNull(selector); | ||
ArgumentNullException.ThrowIfNull(logger); | ||
|
||
var result = new List<TResult>(); | ||
var searchTerm = $"author:{authorName.Value}"; | ||
var searchFilter = new SearchFilter(false); | ||
|
||
var packageCount = 0; | ||
|
||
var skip = 0; | ||
|
||
do | ||
{ | ||
var searchResult = await packageSearchResource.SearchAsync( | ||
searchTerm, | ||
searchFilter, | ||
skip: 0, | ||
take: 1000, | ||
logger, | ||
cancellationToken).ConfigureAwait(false); | ||
|
||
var packages = searchResult.Select(selector) | ||
.ToList(); | ||
|
||
packageCount = packages.Count; | ||
result.AddRange(packages); | ||
|
||
skip += 1000; | ||
} | ||
while (packageCount == 1000); | ||
|
||
return result; | ||
} | ||
} | ||
} |
Oops, something went wrong.