diff --git a/.build/.build.csproj b/.build/.build.csproj index e4a5775a..f0226de5 100644 --- a/.build/.build.csproj +++ b/.build/.build.csproj @@ -1,4 +1,4 @@ - + @@ -23,5 +23,9 @@ + + + + diff --git a/Nuke.sln b/Nuke.sln index 6dab52ad..22fd8ffd 100644 --- a/Nuke.sln +++ b/Nuke.sln @@ -19,9 +19,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = ".build", ".build\.build.csp EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".build", ".build", "{E3DCB78E-EE06-4EA3-88AC-762D8910387C}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rocket.Surgery.Nuke", "src\Nuke\Rocket.Surgery.Nuke.csproj", "{C33C63CF-8760-4305-B9AC-14456930C867}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rocket.Surgery.Nuke", "src\Nuke\Rocket.Surgery.Nuke.csproj", "{C33C63CF-8760-4305-B9AC-14456930C867}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Rocket.Surgery.Nuke.Tests", "test\Nuke.Tests\Rocket.Surgery.Nuke.Tests.csproj", "{29950FB4-8A41-4C83-9FE0-FEA9C8D0BF67}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Rocket.Surgery.Nuke.Tests", "test\Nuke.Tests\Rocket.Surgery.Nuke.Tests.csproj", "{29950FB4-8A41-4C83-9FE0-FEA9C8D0BF67}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/Packages.props b/Packages.props index a4f963f6..55e4e886 100644 --- a/Packages.props +++ b/Packages.props @@ -11,6 +11,8 @@ + + diff --git a/Readme.md b/Readme.md index 01b0f1b7..dd6d8b28 100644 --- a/Readme.md +++ b/Readme.md @@ -1,11 +1,65 @@ -# Rocket Surgeons Guild Nuke +# Rocket Surgery - Nuke -| Build | Test | Release | -|---|---|---| -| | -| [![Nuke Pipelines Build Status](https://img.shields.io/vso/build/RocketSurgeonsGuild/Libraries/RSG.Nuke.svg?logo=visualstudiocode&style=flat-square)](https://rocketsurgeonsguild.visualstudio.com/Libraries/_build?definitionId=3) | | -| | | ![MyGet Pre Release](https://img.shields.io/myget/rocket-surgeons-guild/vpre/Nuke.svg?logo=nuget&style=flat-square&label=myget) | +Every good Rocket Surgeon needs multiple choices of build systems to pick from them for their best work. This is an integration for the `Nuke` build system with some defaults for all Rocket Surgeon Repositories (or if you follow the same structure, you can use it too!) -[![Nuke Pipelines Build History](https://buildstats.info/azurepipelines/chart/RocketSurgeonsGuild/Libraries/3)](https://rocketsurgeonsguild.visualstudio.com/Libraries/_build?definitionId=3) + +[![github-release-badge]][github-release] +[![github-license-badge]][github-license] +[![codecov-badge]][codecov] +[![codacy-badge]][codacy] + -## More info to come... + +| Azure Pipelines | AppVeyor | +| --------------- | -------- | +| [![azurepipelines-badge]][azurepipelines] | [![appveyor-badge]][appveyor] | +| [![azurepipelines-history-badge]][azurepipelines-history] | [![appveyor-history-badge]][appveyor-history] | + + + +| Package | NuGet | MyGet | +| ------- | ----- | ----- | +| Rocket.Surgery.Nuke | [![nuget-version-6plqb7nwtdoa-badge]![nuget-downloads-6plqb7nwtdoa-badge]][nuget-6plqb7nwtdoa] | [![myget-version-6plqb7nwtdoa-badge]![myget-downloads-6plqb7nwtdoa-badge]][myget-6plqb7nwtdoa] | + + + +[github-release]: https://github.com/RocketSurgeonsGuild/Nuke/releases/latest +[github-release-badge]: https://img.shields.io/github/release/RocketSurgeonsGuild/Nuke.svg?logo=github&style=flat "Latest Release" +[github-license]: https://github.com/RocketSurgeonsGuild/Nuke/blob/master/LICENSE +[github-license-badge]: https://img.shields.io/github/license/RocketSurgeonsGuild/Nuke.svg?style=flat "License" +[codecov]: https://codecov.io/gh/RocketSurgeonsGuild/Nuke +[codecov-badge]: https://img.shields.io/codecov/c/github/RocketSurgeonsGuild/Nuke.svg?color=E03997&label=codecov&logo=codecov&logoColor=E03997&style=flat "Code Coverage" +[codacy]: https://www.codacy.com/app/RocketSurgeonsGuild/Nuke +[codacy-badge]: https://api.codacy.com/project/badge/Grade/d31c561959b34f35ae2d99979bfb239a "Codacy" +[azurepipelines]: https://rocketsurgeonsguild.visualstudio.com/Libraries/_build/latest?definitionId=31&branchName=master +[azurepipelines-badge]: https://img.shields.io/azure-devops/build/rocketsurgeonsguild/Libraries/31.svg?color=98C6FF&label=azure%20pipelines&logo=azuredevops&logoColor=98C6FF&style=flat "Azure Pipelines Status" +[azurepipelines-history]: https://rocketsurgeonsguild.visualstudio.com/Libraries/_build?definitionId=31&branchName=master +[azurepipelines-history-badge]: https://buildstats.info/azurepipelines/chart/rocketsurgeonsguild/Libraries/31?includeBuildsFromPullRequest=false "Azure Pipelines History" +[appveyor]: https://ci.appveyor.com/project/RocketSurgeonsGuild/Nuke +[appveyor-badge]: https://img.shields.io/appveyor/ci/RocketSurgeonsGuild/Nuke.svg?color=00b3e0&label=appveyor&logo=appveyor&logoColor=00b3e0&style=flat "AppVeyor Status" +[appveyor-history]: https://ci.appveyor.com/project/RocketSurgeonsGuild/Nuke/history +[appveyor-history-badge]: https://buildstats.info/appveyor/chart/RocketSurgeonsGuild/Nuke?includeBuildsFromPullRequest=false "AppVeyor History" +[nuget-6plqb7nwtdoa]: https://www.nuget.org/packages/Rocket.Surgery.Nuke/ +[nuget-version-6plqb7nwtdoa-badge]: https://img.shields.io/nuget/v/Rocket.Surgery.Nuke.svg?color=004880&logo=nuget&style=flat-square "NuGet Version" +[nuget-downloads-6plqb7nwtdoa-badge]: https://img.shields.io/nuget/dt/Rocket.Surgery.Nuke.svg?color=004880&logo=nuget&style=flat-square "NuGet Downloads" +[myget-6plqb7nwtdoa]: https://www.myget.org/feed/rocket-surgeons-guild/package/nuget/Rocket.Surgery.Nuke +[myget-version-6plqb7nwtdoa-badge]: https://img.shields.io/myget/rocket-surgeons-guild/vpre/Rocket.Surgery.Nuke.svg?label=myget&color=004880&logo=nuget&style=flat-square "MyGet Pre-Release Version" +[myget-downloads-6plqb7nwtdoa-badge]: https://img.shields.io/myget/rocket-surgeons-guild/dt/Rocket.Surgery.Nuke.svg?color=004880&logo=nuget&style=flat-square "MyGet Downloads" + + + \ No newline at end of file diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6005bbc3..8fe2a890 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -30,7 +30,7 @@ variables: - name: DOTNET_SKIP_FIRST_TIME_EXPERIENCE value: "true" - name: CodeCovToken - value: '7dfeb756-b27e-47ec-8906-a349cf7e0688' + value: 'c93f6719-da50-4d00-ba2b-b73fd95239e0' jobs: - template: pipeline/nuke.yml@rsg diff --git a/src/Nuke/Readme/AppVeyorHistory.cs b/src/Nuke/Readme/AppVeyorHistory.cs new file mode 100644 index 00000000..761e6a82 --- /dev/null +++ b/src/Nuke/Readme/AppVeyorHistory.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + class AppVeyorHistory : IHistorySection + { + public string Name { get; } = "AppVeyor"; + + public string ConfigKey { get; } = "appveyor"; + + public (string badge, string history) Process(IDictionary config, IMarkdownReferences references, + RocketBoosterBuild build) + { + var url = references.AddReference("appveyor", $"https://ci.appveyor.com/project/{config["account"]}/{config["build"]}"); + var badge = references.AddReference( + "appveyor-badge", + $"https://img.shields.io/appveyor/ci/{config["account"]}/{config["build"]}.svg?color=00b3e0&label=appveyor&logo=appveyor&logoColor=00b3e0&style=flat", + "AppVeyor Status" + ); + var historyUrl = references.AddReference("appveyor-history", $"https://ci.appveyor.com/project/{config["account"]}/{config["build"]}/history"); + var historyBadge = references.AddReference( + "appveyor-history-badge", + $"https://buildstats.info/appveyor/chart/{config["account"]}/{config["build"]}?includeBuildsFromPullRequest=false", + "AppVeyor History" + ); + + return ($"[!{badge}]{url}", $"[!{historyBadge}]{historyUrl}"); + } + } +} diff --git a/src/Nuke/Readme/AzurePipelinesHistory.cs b/src/Nuke/Readme/AzurePipelinesHistory.cs new file mode 100644 index 00000000..dd95e513 --- /dev/null +++ b/src/Nuke/Readme/AzurePipelinesHistory.cs @@ -0,0 +1,31 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + class AzurePipelinesHistory : IHistorySection + { + public string Name { get; } = "Azure Pipelines"; + + public string ConfigKey { get; } = "azurepipelines"; + + public (string badge, string history) Process(IDictionary config, IMarkdownReferences references, + RocketBoosterBuild build) + { + var url = references.AddReference("azurepipelines", $"https://{config["account"]}.visualstudio.com/{config["teamproject"]}/_build/latest?definitionId={config["builddefinition"]}&branchName=master"); + var badge = references.AddReference( + "azurepipelines-badge", + $"https://img.shields.io/azure-devops/build/{config["account"]}/{config["teamproject"]}/{config["builddefinition"]}.svg?color=98C6FF&label=azure%20pipelines&logo=azuredevops&logoColor=98C6FF&style=flat", + "Azure Pipelines Status" + ); + var historyUrl = references.AddReference("azurepipelines-history", $"https://{config["account"]}.visualstudio.com/{config["teamproject"]}/_build?definitionId={config["builddefinition"]}&branchName=master"); + var historyBadge = references.AddReference( + "azurepipelines-history-badge", + $"https://buildstats.info/azurepipelines/chart/{config["account"]}/{config["teamproject"]}/{config["builddefinition"]}?includeBuildsFromPullRequest=false", + "Azure Pipelines History" + ); + + return ($"[!{badge}]{url}", $"[!{historyBadge}]{historyUrl}"); + } + } +} diff --git a/src/Nuke/Readme/Badges.cs b/src/Nuke/Readme/Badges.cs new file mode 100644 index 00000000..79d5753a --- /dev/null +++ b/src/Nuke/Readme/Badges.cs @@ -0,0 +1,55 @@ +using System.Collections.Generic; +using System.Dynamic; +using System.Text; +using Nuke.Common.Utilities.Collections; + +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// A container for badges that you want to show on the readme + /// + public class Badges : IReadmeSection + { + private readonly List _sections = new List(); + + /// + /// Adds a new Badge section + /// + /// + /// + public Badges Add(IBadgeSection section) + { + _sections.Add(section); + return this; + } + + /// + public string Name => "badges"; + + /// + public string ConfigKey => string.Empty; + + /// + public string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build) + { + var sb = new StringBuilder(); + foreach (var section in _sections) + { + var subConfig = string.IsNullOrEmpty(section.ConfigKey) ? config.ToDictionary(x => (object)x.Key, x => x.Value) : config.TryGetValue(section.ConfigKey, out var o) ? o as IDictionary : null; + // Assume if not configured, it will never be able to be rendered + if (subConfig is null) + { + continue; + } + var result = section.Process(subConfig, references, build); + if (string.IsNullOrWhiteSpace(result)) + { + continue; + } + sb.AppendLine(result); + } + + return sb.ToString(); + } + } +} diff --git a/src/Nuke/Readme/CodacySection.cs b/src/Nuke/Readme/CodacySection.cs new file mode 100644 index 00000000..a3df1043 --- /dev/null +++ b/src/Nuke/Readme/CodacySection.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + class CodacySection : IBadgeSection + { + public string Name => "Codacy"; + + public string ConfigKey => string.Empty; + + public string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build) + { + if (!(config.TryGetValue("github", out var githubObj) && config.TryGetValue("codacy", out var codacyObj))) + { + return string.Empty; + } + + var github = (IDictionary)githubObj; + var codacy = (IDictionary)codacyObj; + var url = references.AddReference("codacy", $"https://www.codacy.com/app/{github["owner"]}/{github["repository"]}"); + var badge = references.AddReference("codacy-badge", $"https://api.codacy.com/project/badge/Grade/{codacy["project"]}", "Codacy"); + return $"[!{badge}]{url}"; + } + } +} diff --git a/src/Nuke/Readme/CodecovSection.cs b/src/Nuke/Readme/CodecovSection.cs new file mode 100644 index 00000000..d49fe56f --- /dev/null +++ b/src/Nuke/Readme/CodecovSection.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + class CodecovSection : IBadgeSection + { + public string Name => "Codecov"; + + public string ConfigKey => "github"; + + public string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build) + { + var url = references.AddReference("codecov", $"https://codecov.io/gh/{config["owner"]}/{config["repository"]}"); + var badge = references.AddReference("codecov-badge", $"https://img.shields.io/codecov/c/github/{config["owner"]}/{config["repository"]}.svg?color=E03997&label=codecov&logo=codecov&logoColor=E03997&style=flat", "Code Coverage"); + return $"[!{badge}]{url}"; + } + } +} diff --git a/src/Nuke/Readme/GithubLicenseSection.cs b/src/Nuke/Readme/GithubLicenseSection.cs new file mode 100644 index 00000000..ebdacf56 --- /dev/null +++ b/src/Nuke/Readme/GithubLicenseSection.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + class GithubLicenseSection : IBadgeSection + { + public string Name => "Github Release"; + + public string ConfigKey => "github"; + + public string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build) + { + var url = references.AddReference("github-license", $"https://github.com/{config["owner"]}/{config["repository"]}/blob/master/LICENSE"); + var badge = references.AddReference("github-license-badge", $"https://img.shields.io/github/license/{config["owner"]}/{config["repository"]}.svg?style=flat", "License"); + return $"[!{badge}]{url}"; + } + } +} diff --git a/src/Nuke/Readme/GithubReleaseSection.cs b/src/Nuke/Readme/GithubReleaseSection.cs new file mode 100644 index 00000000..167e273e --- /dev/null +++ b/src/Nuke/Readme/GithubReleaseSection.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + class GithubReleaseSection : IBadgeSection + { + public string Name => "Github Release"; + + public string ConfigKey => "github"; + + public string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build) + { + var url = references.AddReference("github-release", $"https://github.com/{config["owner"]}/{config["repository"]}/releases/latest"); + var badge = references.AddReference("github-release-badge", $"https://img.shields.io/github/release/{config["owner"]}/{config["repository"]}.svg?logo=github&style=flat", "Latest Release"); + return $"[!{badge}]{url}"; + } + } +} diff --git a/src/Nuke/Readme/Histories.cs b/src/Nuke/Readme/Histories.cs new file mode 100644 index 00000000..9f580561 --- /dev/null +++ b/src/Nuke/Readme/Histories.cs @@ -0,0 +1,57 @@ +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Text; +using JetBrains.Annotations; + +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// A container for build histories that you want to show on the readme + /// + [PublicAPI] + public class Histories : IReadmeSection + { + private readonly List _sections = new List(); + + /// + /// Adds a new history section + /// + /// + /// + public Histories Add(IHistorySection section) + { + _sections.Add(section); + return this; + } + + /// + public string Name => "history badges"; + + /// + public string ConfigKey => string.Empty; + + /// + public string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build) + { + var results = new List<(string name, string badge, string history)>(); + foreach (var section in _sections) + { + var subConfig = string.IsNullOrEmpty(section.ConfigKey) ? config.ToDictionary(x => (object)x.Key, x => x.Value) : config.TryGetValue(section.ConfigKey, out var o) ? o as IDictionary : null; + // Assume if not configured, it will never be able to be rendered + if (subConfig is null) + { + continue; + } + var (badge, history) = section.Process(subConfig, references, build); + results.Add((section.Name, badge, history)); + } + var sb = new StringBuilder(); + sb.AppendLine($"| {string.Join(" | ", results.Select(z => z.name))} |"); + sb.AppendLine($"| {string.Join(" | ", results.Select(z => string.Join("", Enumerable.Range(0, z.name.Length).Select(x => "-"))))} |"); + sb.AppendLine($"| {string.Join(" | ", results.Select(z => z.badge))} |"); + sb.AppendLine($"| {string.Join(" | ", results.Select(z => z.history))} |"); + return sb.ToString(); + } + } +} diff --git a/src/Nuke/Readme/IBadgeSection.cs b/src/Nuke/Readme/IBadgeSection.cs new file mode 100644 index 00000000..f0e0e1f1 --- /dev/null +++ b/src/Nuke/Readme/IBadgeSection.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// Interface is used to add another badge to the `badges` container in the readme. + /// + public interface IBadgeSection + { + /// + /// The name of the section + /// + string Name { get; } + /// + /// The configuration key, if you expect to get configuration from the yaml block. + /// + string ConfigKey { get; } + + /// + /// Returns the markdown that will produce the badge + /// + /// + /// + /// + /// + string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build); + } +} diff --git a/src/Nuke/Readme/IHistorySection.cs b/src/Nuke/Readme/IHistorySection.cs new file mode 100644 index 00000000..cf5dc793 --- /dev/null +++ b/src/Nuke/Readme/IHistorySection.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// Interface is used to add another badge to the `history badges` container in the readme. + /// + public interface IHistorySection + { + /// + /// The name of the section + /// + string Name { get; } + /// + /// The configuration key, if you expect to get configuration from the yaml block. + /// + string ConfigKey { get; } + + /// + /// Returns the markdown that will produce the badge + /// + /// + /// + /// + /// + (string badge, string history) Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build); + } +} diff --git a/src/Nuke/Readme/IMarkdownReferences.cs b/src/Nuke/Readme/IMarkdownReferences.cs new file mode 100644 index 00000000..09a5d4e4 --- /dev/null +++ b/src/Nuke/Readme/IMarkdownReferences.cs @@ -0,0 +1,18 @@ +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// A container interface for markdown references + /// `[somekey]: somevalue "some alt text"` + /// + public interface IMarkdownReferences + { + /// + /// Adds a reference with optional alt text + /// + /// + /// + /// + /// + string AddReference(string name, string value, string altText = null); + } +} diff --git a/src/Nuke/Readme/IReadmeSection.cs b/src/Nuke/Readme/IReadmeSection.cs new file mode 100644 index 00000000..40008f27 --- /dev/null +++ b/src/Nuke/Readme/IReadmeSection.cs @@ -0,0 +1,29 @@ +using System.Collections.Generic; +using System.Dynamic; + +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// Interface is used to add a custom section that will be replaced in the readme with markdown content + /// + public interface IReadmeSection + { + /// + /// The name of the section + /// + string Name { get; } + /// + /// The configuration key, if you expect to get configuration from the yaml block. + /// + string ConfigKey { get; } + + /// + /// Returns the markdown that will produce the badge + /// + /// + /// + /// + /// + string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build); + } +} diff --git a/src/Nuke/Readme/NugetPackagesSection.cs b/src/Nuke/Readme/NugetPackagesSection.cs new file mode 100644 index 00000000..755a2c4a --- /dev/null +++ b/src/Nuke/Readme/NugetPackagesSection.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using Buildalyzer; + +namespace Rocket.Surgery.Nuke.Readme +{ + class NugetPackagesSection : IReadmeSection + { + public string Name { get; } = "nuget packages"; + + public string ConfigKey { get; } = string.Empty; + + public string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build) + { + var am = new AnalyzerManager(build.Solution.Path.ToString(), new AnalyzerManagerOptions()); + + var packageNames = build.Solution.AllProjects + .Where(x => x.Directory.Parent.ToString() == build.SourceDirectory.ToString()) + .Where(x => + { + var projectResults = am.GetProject(x.Path.ToString()).Build().First(); + return bool.TryParse(projectResults.GetProperty("IsPackable") ?? "false", out var b) ? b : false; + }) + .Select(x => x.Name) + .ToArray(); + + var sb = new StringBuilder(); + if (config.ContainsKey("myget")) + { + sb.AppendLine("| Package | NuGet | MyGet |"); + sb.AppendLine("| ------- | ----- | ----- |"); + } + else + { + sb.AppendLine("| Package | NuGet |"); + sb.AppendLine("| ------- | ----- |"); + } + + foreach (var package in packageNames) + { + sb.AppendLine(GetResult(config, references, package)); + } + + return sb.ToString(); + } + public string GetResult(IDictionary config, IMarkdownReferences references, string packageName) + { + var hash = Convert.ToBase64String(MD5.Create().ComputeHash(Encoding.ASCII.GetBytes(packageName))).Replace("=", "").Substring(10).ToLower(); + var nugetUrlReference = references.AddReference($"nuget-{hash}", NugetUrl(packageName)); + var nugetVersionBadge = references.AddReference($"nuget-version-{hash}-badge", NuGetVersionBadge(packageName), "NuGet Version"); + var nugetDownloadsBadge = references.AddReference($"nuget-downloads-{hash}-badge", NuGetDownloadsBadge(packageName), "NuGet Downloads"); + if (!config.ContainsKey("myget")) + { + return $"| {packageName} | [!{nugetVersionBadge}!{nugetDownloadsBadge}]{nugetUrlReference} |"; + } + + var dcfg = config; + var myget = dcfg["myget"] as IDictionary; + var mygetUrlReference = references.AddReference($"myget-{hash}", MyGetUrl(myget["account"].ToString(), packageName)); + var mygetVersionBadge = references.AddReference($"myget-version-{hash}-badge", MyGetPrereleaseVersionBadge(myget["account"].ToString(), packageName), "MyGet Pre-Release Version"); + var mygetDownloadsBadge = references.AddReference($"myget-downloads-{hash}-badge", MyGetDownloadsBadge(myget["account"].ToString(), packageName), "MyGet Downloads"); + return $"| {packageName} | [!{nugetVersionBadge}!{nugetDownloadsBadge}]{nugetUrlReference} | [!{mygetVersionBadge}!{mygetDownloadsBadge}]{mygetUrlReference} |"; + } + + private static string NugetUrl(string packageName) => $"https://www.nuget.org/packages/{packageName}/"; + private static string NuGetDownloadsBadge(string packageName) => $"https://img.shields.io/nuget/dt/{packageName}.svg?color=004880&logo=nuget&style=flat-square"; + private static string NuGetVersionBadge(string packageName) => $"https://img.shields.io/nuget/v/{packageName}.svg?color=004880&logo=nuget&style=flat-square"; + private static string NuGetPrereleaseVersionBadge(string packageName) => $"https://img.shields.io/nuget/vpre/{packageName}.svg?color=004880&logo=nuget&style=flat-square"; + private static string MyGetUrl(string project, string packageName) => $"https://www.myget.org/feed/{project}/package/nuget/{packageName}"; + private static string MyGetDownloadsBadge(string project, string packageName) => $"https://img.shields.io/myget/{project}/dt/{packageName}.svg?color=004880&logo=nuget&style=flat-square"; + private static string MyGetVersionBadge(string project, string packageName) => $"https://img.shields.io/myget/{project}/v/{packageName}.svg?label=myget&color=004880&logo=nuget&style=flat-square"; + private static string MyGetPrereleaseVersionBadge(string project, string packageName) => $"https://img.shields.io/myget/{project}/vpre/{packageName}.svg?label=myget&color=004880&logo=nuget&style=flat-square"; + } +} diff --git a/src/Nuke/Readme/ReadmeAttribute.cs b/src/Nuke/Readme/ReadmeAttribute.cs new file mode 100644 index 00000000..d3676058 --- /dev/null +++ b/src/Nuke/Readme/ReadmeAttribute.cs @@ -0,0 +1,28 @@ +using System.Diagnostics; +using System.Reflection; +using Nuke.Common.ProjectModel; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Nuke.Common.Execution; +using Nuke.Common.Tooling; +using Nuke.Common.Tools.GitVersion; + +namespace Rocket.Surgery.Nuke.Readme +{ + + /// + /// Injects an instance of based on the local repository. + /// + [PublicAPI] + [UsedImplicitly(ImplicitUseKindFlags.Default)] + internal class ReadmeAttribute : InjectionAttributeBase + { + /// + public override object GetValue(MemberInfo member, object instance) + { + return new ReadmeUpdater(); + } + } +} diff --git a/src/Nuke/Readme/ReadmeUpdater.cs b/src/Nuke/Readme/ReadmeUpdater.cs new file mode 100644 index 00000000..8848c741 --- /dev/null +++ b/src/Nuke/Readme/ReadmeUpdater.cs @@ -0,0 +1,113 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.IO; +using System.Linq; +using System.Text.RegularExpressions; +using JetBrains.Annotations; +using Nuke.Common; +using YamlDotNet.Serialization; + +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// The generic class used to contain all the sections, badges, histories and references. + /// + [PublicAPI] + public class ReadmeUpdater + { + /// + /// Default constructor + /// + public ReadmeUpdater() + { + Sections = new Sections(); + Badges = new Badges(); + History = new Histories(); + References = new References(); + Sections + .Add(Badges) + .Add(History) + .Add(References) + ; + Sections.Add(new NugetPackagesSection()); + History + .Add(new AzurePipelinesHistory()) + .Add(new AppVeyorHistory()) + ; + Badges + .Add(new GithubReleaseSection()) + .Add(new GithubLicenseSection()) + .Add(new CodecovSection()) + .Add(new CodacySection()) + ; + } + + /// + /// The sections container + /// + public Sections Sections { get; } + /// + /// The badges container + /// + public Badges Badges { get; } + /// + /// The history container + /// + public Histories History { get; set; } + /// + /// The references container for markdown references + /// + public References References { get; } + + /// + /// Updates the given markdown content with all the sections replaced. + /// + /// The "generated references" is special and will always be run through last, to make sure all sections can contribute references. + /// + /// + /// + /// + public string Process(string content, RocketBoosterBuild build) + { + var nukeDataRegex = new Regex("", RegexOptions.Singleline | RegexOptions.Compiled | RegexOptions.IgnoreCase); + var match = nukeDataRegex.Match(content); + var yaml = string.Join("\n", match.Groups.Cast().Skip(1).Select(x => x.Value)); + var d = new DeserializerBuilder() + // .WithNamingConvention(new CamelCaseNamingConvention()) + .Build(); + var config = d.Deserialize(new StringReader(yaml.Trim('\n'))); + + var sectionRegex = new Regex("", RegexOptions.Multiline | RegexOptions.Compiled | RegexOptions.IgnoreCase); + + var sections = sectionRegex.Matches(content); + + var ranges = new List<(int start, int length, string content)>(); + foreach (var sectionMatch in sections + .Cast() + .GroupBy(x => x.Groups[1].Value) + .OrderByDescending(x => x.Key != "generated references") + ) + { + var sectionName = sectionMatch.First().Groups[1].Value; + if (!Sections.AllSections.TryGetValue(sectionName, out var section)) + { + throw new NotImplementedException("Section " + sectionName + " is not supported!"); + } + + var sectionStart = sectionMatch.First().Captures[0]; + var sectionEnd = sectionMatch.Last().Captures[0]; + var newSectionContent = section.Process(config, References, build); + ranges.Add((sectionStart.Index + sectionStart.Length, sectionEnd.Index - (sectionStart.Index + sectionStart.Length), newSectionContent)); + } + + foreach (var range in ranges.OrderByDescending(x => x.start)) + { + content = content.Substring(0, range.start) + + "\n" + range.content + content.Substring(range.start + range.length); + } + + return content; + } + } +} diff --git a/src/Nuke/Readme/References.cs b/src/Nuke/Readme/References.cs new file mode 100644 index 00000000..12eb0a54 --- /dev/null +++ b/src/Nuke/Readme/References.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Dynamic; +using System.Text; + +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// This section is used to allow for clean markdown references so that the readme file isn't complete cluttered with image urls and links. + /// + public class References : IMarkdownReferences, IReadmeSection + { + private readonly Dictionary _references = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + public string Name { get; } = "generated references"; + + /// + public string ConfigKey { get; } = string.Empty; + + /// + public string AddReference(string name, string value, string altText = null) + { + var key = $"[{name}]"; + if (string.IsNullOrEmpty(altText)) + { + altText = ""; + } + else + { + altText = $" \"{altText}\""; + } + _references.Add(key, $"{value}{altText}"); + return key; + } + + /// + public string Process(IDictionary config, IMarkdownReferences references, RocketBoosterBuild build) + { + var sb = new StringBuilder(); + foreach (var item in _references) + { + sb.AppendLine($"{item.Key}: {item.Value}"); + } + return sb.ToString(); + } + } +} diff --git a/src/Nuke/Readme/Sections.cs b/src/Nuke/Readme/Sections.cs new file mode 100644 index 00000000..38f95136 --- /dev/null +++ b/src/Nuke/Readme/Sections.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using JetBrains.Annotations; + +namespace Rocket.Surgery.Nuke.Readme +{ + /// + /// A general sections container, used to replace sections in the markdown + /// + [PublicAPI] + public class Sections + { + private readonly IDictionary _sections = new Dictionary(StringComparer.OrdinalIgnoreCase); + + /// + /// Adds a new section. + /// + /// + /// + public Sections Add(IReadmeSection section) + { + _sections.Add(section.Name, section); + return this; + } + + /// + /// Gets a list of all the sections for markdown use + /// + internal IReadOnlyDictionary AllSections => new ReadOnlyDictionary(_sections); + } +} diff --git a/src/Nuke/Rocket.Surgery.Nuke.csproj b/src/Nuke/Rocket.Surgery.Nuke.csproj index 1030b423..dce1eab0 100644 --- a/src/Nuke/Rocket.Surgery.Nuke.csproj +++ b/src/Nuke/Rocket.Surgery.Nuke.csproj @@ -1,4 +1,4 @@ - + netstandard2.0 $(NoWarn);CS0436 @@ -7,6 +7,8 @@ + + diff --git a/src/Nuke/RocketBoosterBuild.cs b/src/Nuke/RocketBoosterBuild.cs index 33fe5dc8..d856b3b4 100644 --- a/src/Nuke/RocketBoosterBuild.cs +++ b/src/Nuke/RocketBoosterBuild.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using Nuke.Common; using Nuke.Common.Execution; using Nuke.Common.Git; @@ -14,12 +14,15 @@ using System.IO; using System.Linq; using System.ComponentModel; +using JetBrains.Annotations; +using Rocket.Surgery.Nuke.Readme; namespace Rocket.Surgery.Nuke { /// /// Base build plan and tasks /// + [PublicAPI] public abstract class RocketBoosterBuild : NukeBuild { /// @@ -28,6 +31,12 @@ public abstract class RocketBoosterBuild : NukeBuild [Parameter("Configuration to build - Default is 'Debug' (local) or 'Release' (server)")] public readonly Configuration Configuration = IsLocalBuild ? Configuration.Debug : Configuration.Release; + /// + /// Force a clean build, otherwise leave some incremental build pieces + /// + [Parameter("Force a clean build")] + public readonly bool Force; + /// /// The solution currently being build /// @@ -43,6 +52,12 @@ public abstract class RocketBoosterBuild : NukeBuild /// [ComputedGitVersion] public readonly GitVersion GitVersion; + /// + /// The readme updater that ensures that all the badges are in sync. + /// + + [Readme] public readonly ReadmeUpdater Readme; + /// /// The directory where samples will be placed /// @@ -100,17 +115,20 @@ public abstract class RocketBoosterBuild : NukeBuild EnsureCleanDirectory(ArtifactsDirectory); EnsureCleanDirectory(CoverageDirectory); - EnsureExistingDirectory(SampleDirectory); - SampleDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); + if (Force) + { + EnsureExistingDirectory(SampleDirectory); + SampleDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); - EnsureExistingDirectory(SourceDirectory); - SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); + EnsureExistingDirectory(SourceDirectory); + SourceDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); - EnsureExistingDirectory(TemplatesDirectory); - TemplatesDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); + EnsureExistingDirectory(TemplatesDirectory); + TemplatesDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); - EnsureExistingDirectory(TestDirectory); - TestDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); + EnsureExistingDirectory(TestDirectory); + TestDirectory.GlobDirectories("**/bin", "**/obj").ForEach(DeleteDirectory); + } }); @@ -153,5 +171,19 @@ public abstract class RocketBoosterBuild : NukeBuild ); RenameFile(CoverageDirectory / "Cobertura.xml", "solution.xml"); }); + + /// + /// Loops through the Readme to update sections that are automated to give nuget packages, build histories and more, while keeping the rest of the readme correct. + /// + public Target GenerateReadme => _ => _ + .Unlisted() + .TriggeredBy(Clean) + .OnlyWhenDynamic(() => IsLocalBuild) + .Executes(() => + { + var readmeContent = File.ReadAllText(RootDirectory / "Readme.md"); + readmeContent = Readme.Process(readmeContent, this); + File.WriteAllText(RootDirectory / "Readme.md", readmeContent); + }); } }