From d12cf388ec5f19bfefc0c40ac76ec5fd41357889 Mon Sep 17 00:00:00 2001 From: Rasmus Mikkelsen Date: Thu, 24 Feb 2022 20:01:50 +0100 Subject: [PATCH] Limit NuGet description length (#80) --- .github/workflows/pull-requests.yml | 2 +- .github/workflows/release.yml | 2 +- RELEASE_NOTES.md | 4 + .../DotNetComposerServiceTests.cs | 3 +- .../Services/DescriptionLimiterTests.cs | 117 ++++++++++++++++++ .../Bake/Cooking/Composers/DotNetComposer.cs | 12 +- .../Extensions/ServiceCollectionExtensions.cs | 1 + Source/Bake/Services/DescriptionLimiter.cs | 97 +++++++++++++++ Source/Bake/Services/IDescriptionLimiter.cs | 29 +++++ 9 files changed, 260 insertions(+), 7 deletions(-) create mode 100644 Source/Bake.Tests/UnitTests/Services/DescriptionLimiterTests.cs create mode 100644 Source/Bake/Services/DescriptionLimiter.cs create mode 100644 Source/Bake/Services/IDescriptionLimiter.cs diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index 915c8f67..33ed9e41 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -57,7 +57,7 @@ jobs: run: dotnet publish -o bake-it Source/Bake - name: Run Bake - run: bake-it/bake run --build-version 0.11.$GITHUB_RUN_NUMBER + run: bake-it/bake run --build-version 0.12.$GITHUB_RUN_NUMBER - name: Upload test results uses: actions/upload-artifact@v2 diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 65da5d3e..1eebe644 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,7 +57,7 @@ jobs: run: dotnet publish -o bake-it Source/Bake - name: Run Bake - run: bake-it/bake run --convention=Release --build-version 0.11.$GITHUB_RUN_NUMBER --destination="nuget>github,nuget,release>github" + run: bake-it/bake run --convention=Release --build-version 0.12.$GITHUB_RUN_NUMBER --destination="nuget>github,nuget,release>github" - name: Upload test results uses: actions/upload-artifact@v2 diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 16cac55e..9c80d670 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,3 +1,7 @@ +# 0.12-beta + +* Fixed: Not limits description of NuGet packages to 4.000 characters + # 0.11-beta * New: Use content of `README.md` files as DLL and NuGet descriptions diff --git a/Source/Bake.Tests/IntegrationTests/ServiceTests/DotNetComposerServiceTests.cs b/Source/Bake.Tests/IntegrationTests/ServiceTests/DotNetComposerServiceTests.cs index 669f65ac..2468a89a 100644 --- a/Source/Bake.Tests/IntegrationTests/ServiceTests/DotNetComposerServiceTests.cs +++ b/Source/Bake.Tests/IntegrationTests/ServiceTests/DotNetComposerServiceTests.cs @@ -39,7 +39,7 @@ public DotNetComposerServiceTests() : base("NetCore.Console") } [Test] - public async Task T() + public async Task TestIt() { // Arrange var ingredients = Ingredients.New( @@ -63,6 +63,7 @@ protected override IServiceCollection Configure(IServiceCollection serviceCollec .AddTransient() .AddTransient() .AddTransient() + .AddTransient() .AddSingleton(TestEnvironmentVariables.None) .AddTransient() .AddTransient() diff --git a/Source/Bake.Tests/UnitTests/Services/DescriptionLimiterTests.cs b/Source/Bake.Tests/UnitTests/Services/DescriptionLimiterTests.cs new file mode 100644 index 00000000..228a9b07 --- /dev/null +++ b/Source/Bake.Tests/UnitTests/Services/DescriptionLimiterTests.cs @@ -0,0 +1,117 @@ +// MIT License +// +// Copyright (c) 2021-2022 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using Bake.Services; +using Bake.Tests.Helpers; +using FluentAssertions; +using NUnit.Framework; + +// ReSharper disable StringLiteralTypo + +namespace Bake.Tests.UnitTests.Services +{ + public class DescriptionLimiterTests : TestFor + { + [Test] + public void LessThanMaxIsAccepted() + { + // Arrange + var text = A(); + + // Act + var output = Sut.Limit(text, int.MaxValue); + + // Assert + output.Should().Be(text); + } + + [Test] + public void CutsAtHeadlineSimple() + { + // Arrange + var text = Concat( + "#", + new string('x', 5), + "#", + new string('y', 10)); + + // Act + var output = Sut.Limit(text, 15); + + // Assert + AssertEquals( + output, + "#", + new string('x', 5)); + } + + [Test] + public void CutsAtHeadline() + { + // Arrange + var text = Concat( + "#", + new string('x', 10), + "#", + new string('y', 10), + "#", + new string('z', 10), + "#", + new string('i', 10)); + + // Act + var output = Sut.Limit(text, 36); + + // Assert + AssertEquals( + output, + "#", + new string('x', 10), + "#", + new string('y', 10)); + } + + [Test] + public void Fallback() + { + // Arrange + var text = new string('x', 42); + + // Act + var output = Sut.Limit(text, 40); + + // Assert + output.Should().Be(new string('x', 40)); + } + + private static string Concat(params string[] texts) => string.Join(Environment.NewLine, texts); + + private static void AssertEquals( + string input, + params string[] expected) + { + var inputLines = input.Split(new[] {'\r', '\n'}, StringSplitOptions.RemoveEmptyEntries); + inputLines.Should().BeEquivalentTo(expected, o => o.WithStrictOrdering()); + } + } +} diff --git a/Source/Bake/Cooking/Composers/DotNetComposer.cs b/Source/Bake/Cooking/Composers/DotNetComposer.cs index ce6932ff..ad5c5b18 100644 --- a/Source/Bake/Cooking/Composers/DotNetComposer.cs +++ b/Source/Bake/Cooking/Composers/DotNetComposer.cs @@ -43,6 +43,7 @@ namespace Bake.Cooking.Composers { public class DotNetComposer : Composer { + public const int MaximumNuGetDescription = 4000; private static readonly IReadOnlyDictionary DefaultProperties = new Dictionary { // We really don't want builds to fail due to old versions @@ -70,6 +71,7 @@ public class DotNetComposer : Composer private readonly IConventionInterpreter _conventionInterpreter; private readonly IDefaults _defaults; private readonly IDockerLabels _dockerLabels; + private readonly IDescriptionLimiter _descriptionLimiter; public DotNetComposer( ILogger logger, @@ -77,7 +79,8 @@ public DotNetComposer( ICsProjParser csProjParser, IConventionInterpreter conventionInterpreter, IDefaults defaults, - IDockerLabels dockerLabels) + IDockerLabels dockerLabels, + IDescriptionLimiter descriptionLimiter) { _logger = logger; _fileSystem = fileSystem; @@ -85,6 +88,7 @@ public DotNetComposer( _conventionInterpreter = conventionInterpreter; _defaults = defaults; _dockerLabels = dockerLabels; + _descriptionLimiter = descriptionLimiter; } public override async Task> ComposeAsync( @@ -312,7 +316,7 @@ private static Recipe CreateBuildRecipe( properties); } - private static Dictionary CreateProperties( + private Dictionary CreateProperties( ValueObjects.Ingredients ingredients, VisualStudioSolution visualStudioSolution) { @@ -348,13 +352,13 @@ private static Dictionary CreateProperties( return properties; } - private static string BuildDescription( + private string BuildDescription( VisualStudioSolution visualStudioSolution, ValueObjects.Ingredients ingredients) { if (ingredients.Description != null) { - return ingredients.Description.Text; + return _descriptionLimiter.Limit(ingredients.Description.Text, MaximumNuGetDescription); } var elements = new Dictionary diff --git a/Source/Bake/Extensions/ServiceCollectionExtensions.cs b/Source/Bake/Extensions/ServiceCollectionExtensions.cs index 4f6d3cd8..3760febd 100644 --- a/Source/Bake/Extensions/ServiceCollectionExtensions.cs +++ b/Source/Bake/Extensions/ServiceCollectionExtensions.cs @@ -79,6 +79,7 @@ public static IServiceCollection AddBake( .AddTransient() .AddTransient() .AddTransient() + .AddTransient() // Gathers .AddTransient() diff --git a/Source/Bake/Services/DescriptionLimiter.cs b/Source/Bake/Services/DescriptionLimiter.cs new file mode 100644 index 00000000..a63e262c --- /dev/null +++ b/Source/Bake/Services/DescriptionLimiter.cs @@ -0,0 +1,97 @@ +// MIT License +// +// Copyright (c) 2021-2022 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +using System; +using System.Collections.Generic; +using System.Linq; +using Microsoft.Extensions.Logging; + +namespace Bake.Services +{ + public class DescriptionLimiter : IDescriptionLimiter + { + private readonly ILogger _logger; + + public DescriptionLimiter( + ILogger logger) + { + _logger = logger; + } + + public string Limit(string markdown, int maxLength) + { + if (string.IsNullOrEmpty(markdown) || + markdown.Length <= maxLength) + { + return markdown ?? string.Empty; + } + + var lines = markdown + .Split('\n', '\r'); + var totalHeadlines = lines.Count(l => l.StartsWith("#")); + + for (var i = totalHeadlines; 0 <= i; i--) + { + var text = string.Join(Environment.NewLine, TakeHeadlines(lines, i)); + if (string.IsNullOrWhiteSpace(text)) + { + break; + } + if (text.Length <= maxLength) + { + _logger.LogWarning( + "Description text with its length of {CurrentLength} is too long compared to the maximum {MaximumLength}, cutting it at closest header to {NewLength}", + markdown.Length, + maxLength, + text.Length); + return text; + } + } + + _logger.LogWarning( + "Description text with its length of {CurrentLength} is too long compared to the maximum {MaximumLength}, but cannot find a good markdown header to cut at so cutting it in the middle of text at {NewLength}", + markdown.Length, + maxLength, + maxLength); + + return markdown[..maxLength]; + } + + private static IEnumerable TakeHeadlines(IEnumerable lines, int headlines) + { + var currentHeadline = 0; + foreach (var line in lines) + { + if (line.StartsWith("#")) + { + currentHeadline++; + } + if (headlines < currentHeadline) + { + yield break; + } + + yield return line; + } + } + } +} diff --git a/Source/Bake/Services/IDescriptionLimiter.cs b/Source/Bake/Services/IDescriptionLimiter.cs new file mode 100644 index 00000000..555f082a --- /dev/null +++ b/Source/Bake/Services/IDescriptionLimiter.cs @@ -0,0 +1,29 @@ +// MIT License +// +// Copyright (c) 2021-2022 Rasmus Mikkelsen +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. + +namespace Bake.Services +{ + public interface IDescriptionLimiter + { + string Limit(string markdown, int maxLength); + } +}