diff --git a/README.md b/README.md index 0c55226..96b8868 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # dotnet-thirdpartynotices [![NuGet](https://img.shields.io/nuget/v/DotnetThirdPartyNotices.svg)](https://www.nuget.org/packages/DotnetThirdPartyNotices/) -[![NuGet downloads](https://img.shields.io/nuget/dt/SharpIppNext.svg)](https://www.nuget.org/packages/SharpIppNext) -[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/danielklecha/SharpIppNext/blob/master/LICENSE.txt) +[![NuGet downloads](https://img.shields.io/nuget/dt/DotnetThirdPartyNotices.svg)](https://www.nuget.org/packages/DotnetThirdPartyNotices/) +[![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/bugproof/DotnetThirdPartyNotices/blob/master/LICENSE) ![bcs](https://i.giphy.com/media/giFr1HNq8p5gOQ3nCv/200.gif) diff --git a/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj b/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj index 89a8aa6..9ceb646 100644 --- a/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj +++ b/src/DotnetThirdPartyNotices/DotnetThirdPartyNotices.csproj @@ -10,7 +10,7 @@ dotnet-thirdpartynotices DotnetThirdPartyNotices A .NET tool to generate file with third party legal notices - 0.3.0 + 0.3.1 MIT git https://github.com/bugproof/DotnetThirdPartyNotices diff --git a/src/DotnetThirdPartyNotices/Program.cs b/src/DotnetThirdPartyNotices/Program.cs index 43986ed..3918199 100644 --- a/src/DotnetThirdPartyNotices/Program.cs +++ b/src/DotnetThirdPartyNotices/Program.cs @@ -14,7 +14,7 @@ Log.Logger = new LoggerConfiguration() .MinimumLevel.Warning() - .MinimumLevel.Override("DotnetThirdPartyNotices", LogEventLevel.Debug) + .MinimumLevel.Override("DotnetThirdPartyNotices", LogEventLevel.Information) .Enrich.FromLogContext() .WriteTo.Console() .CreateLogger(); diff --git a/src/DotnetThirdPartyNotices/Services/LicenseService.cs b/src/DotnetThirdPartyNotices/Services/LicenseService.cs index a31c43b..c534dc4 100644 --- a/src/DotnetThirdPartyNotices/Services/LicenseService.cs +++ b/src/DotnetThirdPartyNotices/Services/LicenseService.cs @@ -1,9 +1,10 @@ using DotnetThirdPartyNotices.Models; +using Microsoft.Extensions.Logging; using System.Text.RegularExpressions; namespace DotnetThirdPartyNotices.Services; -internal partial class LicenseService(IEnumerable licenseUriLicenseResolvers, IEnumerable projectUriLicenseResolvers, IEnumerable repositoryUriLicenseResolvers, IEnumerable fileVersionInfoLicenseResolvers, IHttpClientFactory httpClientFactory) : ILicenseService +internal partial class LicenseService(ILogger logger, IEnumerable licenseUriLicenseResolvers, IEnumerable projectUriLicenseResolvers, IEnumerable repositoryUriLicenseResolvers, IEnumerable fileVersionInfoLicenseResolvers, IHttpClientFactory httpClientFactory) : ILicenseService { private static readonly Dictionary LicenseCache = []; @@ -17,7 +18,7 @@ internal partial class LicenseService(IEnumerable li ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, false)) ?? (await ResolveFromProjectUrlAsync(resolvedFileInfo, false)) ?? (await ResolveFromPackagePathAsync(resolvedFileInfo)) - ?? (await ResolveFromAssemblyPathAsync(resolvedFileInfo)) + ?? (await ResolveFromSourcePathAsync(resolvedFileInfo)) ?? (await ResolveFromFileVersionInfoAsync(resolvedFileInfo)) ?? (await ResolveFromLicenseUrlAsync(resolvedFileInfo, true)) ?? (await ResolveFromRepositoryUrlAsync(resolvedFileInfo, true)) @@ -41,7 +42,7 @@ internal partial class LicenseService(IEnumerable li { if (current == 0 || string.IsNullOrWhiteSpace(line)) return current; - int spacesCount = line.TakeWhile(char.IsWhiteSpace).Count(); + int spacesCount = line.TakeWhile(x => x == ' ').Count(); return current == null ? spacesCount : Math.Min(spacesCount, current.Value); }); if (leadingSpacesCount > 0) @@ -72,30 +73,38 @@ internal partial class LicenseService(IEnumerable li return license; } - private async Task ResolveFromAssemblyPathAsync(ResolvedFileInfo resolvedFileInfo) + private async Task ResolveFromSourcePathAsync(ResolvedFileInfo resolvedFileInfo) { if (string.IsNullOrEmpty(resolvedFileInfo.SourcePath)) return null; if (LicenseCache.TryGetValue(resolvedFileInfo.SourcePath, out string? value)) return value; - var directoryPath = Path.GetDirectoryName(resolvedFileInfo.SourcePath); - if(directoryPath == null) - return null; - var fileName = Path.GetFileNameWithoutExtension(resolvedFileInfo.SourcePath) ?? string.Empty; - Regex regex = new($"\\\\(|{Regex.Escape(fileName)}[+-_])license\\.(txt|md)$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(300)); - var licensePath = Directory.EnumerateFiles(directoryPath, "*.*", new EnumerationOptions + try { - MatchCasing = MatchCasing.CaseInsensitive, - RecurseSubdirectories = false - }).FirstOrDefault(regex.IsMatch); - if (licensePath == null) - return null; - var license = await File.ReadAllTextAsync(licensePath); - license = UnifyLicense(license); - if (license == null) + var directoryPath = Path.GetDirectoryName(resolvedFileInfo.SourcePath); + if (directoryPath == null) + return null; + var fileName = Path.GetFileNameWithoutExtension(resolvedFileInfo.SourcePath) ?? string.Empty; + Regex regex = new($"\\\\(|{Regex.Escape(fileName)}[+-_])license\\.(txt|md)$", RegexOptions.IgnoreCase, TimeSpan.FromMilliseconds(300)); + var licensePath = Directory.EnumerateFiles(directoryPath, "*.*", new EnumerationOptions + { + MatchCasing = MatchCasing.CaseInsensitive, + RecurseSubdirectories = false + }).FirstOrDefault(regex.IsMatch); + if (licensePath == null) + return null; + var license = await File.ReadAllTextAsync(licensePath); + license = UnifyLicense(license); + if (license == null) + return null; + LicenseCache[directoryPath] = license; + return license; + } + catch (Exception ex) + { + logger.LogDebug(ex, "Unable to resolve {path}", resolvedFileInfo.SourcePath); return null; - LicenseCache[directoryPath] = license; - return license; + } } private async Task ResolveFromFileVersionInfoAsync(ResolvedFileInfo resolvedFileInfo) @@ -104,19 +113,27 @@ internal partial class LicenseService(IEnumerable li return null; if (LicenseCache.TryGetValue(resolvedFileInfo.VersionInfo.FileName, out string? value)) return value; - foreach (var resolver in fileVersionInfoLicenseResolvers) + try { - if (!resolver.CanResolve(resolvedFileInfo.VersionInfo)) - continue; - var license = await resolver.Resolve(resolvedFileInfo.VersionInfo); - license = UnifyLicense(license); - if (license != null) + foreach (var resolver in fileVersionInfoLicenseResolvers) { - LicenseCache[resolvedFileInfo.VersionInfo.FileName] = license; - return license; + if (!resolver.CanResolve(resolvedFileInfo.VersionInfo)) + continue; + var license = await resolver.Resolve(resolvedFileInfo.VersionInfo); + license = UnifyLicense(license); + if (license != null) + { + LicenseCache[resolvedFileInfo.VersionInfo.FileName] = license; + return license; + } } + return null; + } + catch (Exception ex) + { + logger.LogDebug(ex, "Unable to resolve {path}", resolvedFileInfo.VersionInfo.FileName); + return null; } - return null; } private async Task ResolveFromLicenseRelativePathAsync(ResolvedFileInfo resolvedFileInfo) @@ -124,18 +141,26 @@ internal partial class LicenseService(IEnumerable li if (string.IsNullOrEmpty(resolvedFileInfo.PackagePath) || string.IsNullOrEmpty(resolvedFileInfo.NuSpec?.LicenseRelativePath)) return null; var licenseFullPath = Path.Combine(resolvedFileInfo.PackagePath, resolvedFileInfo.NuSpec.LicenseRelativePath); - if (LicenseCache.TryGetValue(licenseFullPath, out string? value)) - return value; - if (!licenseFullPath.EndsWith(".txt") && !licenseFullPath.EndsWith(".md") || !File.Exists(licenseFullPath)) - return null; - var license = await File.ReadAllTextAsync(licenseFullPath); - license = UnifyLicense(license); - if (license == null) + try + { + if (LicenseCache.TryGetValue(licenseFullPath, out string? value)) + return value; + if (!licenseFullPath.EndsWith(".txt") && !licenseFullPath.EndsWith(".md") || !File.Exists(licenseFullPath)) + return null; + var license = await File.ReadAllTextAsync(licenseFullPath); + license = UnifyLicense(license); + if (license == null) + return null; + license = license.Trim(); + LicenseCache[resolvedFileInfo.NuSpec.Id] = license; + LicenseCache[licenseFullPath] = license; + return license; + } + catch (Exception ex) + { + logger.LogDebug(ex, "Unable to resolve {path}", licenseFullPath); return null; - license = license.Trim(); - LicenseCache[resolvedFileInfo.NuSpec.Id] = license; - LicenseCache[licenseFullPath] = license; - return license; + } } private async Task ResolveFromProjectUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl) @@ -146,15 +171,23 @@ internal partial class LicenseService(IEnumerable li return value; if (!Uri.TryCreate(resolvedFileInfo.NuSpec.ProjectUrl, UriKind.Absolute, out var uri)) return null; - var license = useFinalUrl + try + { + var license = useFinalUrl ? await ResolveFromFinalUrlAsync(uri, projectUriLicenseResolvers) : await ResolveFromUrlAsync(uri, projectUriLicenseResolvers); - license = UnifyLicense(license); - if (license == null) + license = UnifyLicense(license); + if (license == null) + return null; + LicenseCache[resolvedFileInfo.NuSpec.Id] = license; + LicenseCache[resolvedFileInfo.NuSpec.ProjectUrl] = license; + return license; + } + catch (Exception ex) + { + logger.LogDebug(ex, "Unable to resolve {url}", resolvedFileInfo.NuSpec.ProjectUrl); return null; - LicenseCache[resolvedFileInfo.NuSpec.Id] = license; - LicenseCache[resolvedFileInfo.NuSpec.ProjectUrl] = license; - return license; + } } private async Task ResolveFromUrlAsync(Uri uri, IEnumerable urlLicenseResolvers) @@ -211,16 +244,23 @@ internal partial class LicenseService(IEnumerable li return value; if (!Uri.TryCreate(resolvedFileInfo.NuSpec.RepositoryUrl, UriKind.Absolute, out var uri)) return null; - var license = useFinalUrl + try + { + var license = useFinalUrl ? await ResolveFromFinalUrlAsync(uri, repositoryUriLicenseResolvers) : await ResolveFromUrlAsync(uri, repositoryUriLicenseResolvers); - license = UnifyLicense(license); - if (license != null) - { + license = UnifyLicense(license); + if (license == null) + return null; LicenseCache[resolvedFileInfo.NuSpec.Id] = license; LicenseCache[resolvedFileInfo.NuSpec.RepositoryUrl] = license; + return license; + } + catch (Exception ex) + { + logger.LogDebug(ex, "Unable to resolve {url}", resolvedFileInfo.NuSpec.RepositoryUrl); + return null; } - return license; } private async Task ResolveFromLicenseUrlAsync(ResolvedFileInfo resolvedFileInfo, bool useFinalUrl) @@ -231,15 +271,23 @@ internal partial class LicenseService(IEnumerable li return value; if (!Uri.TryCreate(resolvedFileInfo.NuSpec.LicenseUrl, UriKind.Absolute, out var uri)) return null; - var license = useFinalUrl + try + { + var license = useFinalUrl ? await ResolveFromFinalUrlAsync(uri, licenseUriLicenseResolvers) : await ResolveFromUrlAsync(uri, licenseUriLicenseResolvers); - license = UnifyLicense(license); - if (license == null) + license = UnifyLicense(license); + if (license == null) + return null; + LicenseCache[resolvedFileInfo.NuSpec.Id] = license; + LicenseCache[resolvedFileInfo.NuSpec.LicenseUrl] = license; + return license; + } + catch (Exception ex) + { + logger.LogDebug(ex, "Unable to resolve {url}", resolvedFileInfo.NuSpec.LicenseUrl); return null; - LicenseCache[resolvedFileInfo.NuSpec.Id] = license; - LicenseCache[resolvedFileInfo.NuSpec.LicenseUrl] = license; - return license; + } } [GeneratedRegex(@"\\license\.(txt|md)$", RegexOptions.IgnoreCase, 300)] private static partial Regex LicenseFileRegex();