diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index bb1a746..a8f1b80 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -2,11 +2,11 @@ "version": 1, "isRoot": true, "tools": { - "fantomas-tool": { - "version": "4.6.0-beta-001", + "fantomas": { + "version": "6.3.12", "commands": [ "fantomas" ] } } -} \ No newline at end of file +} diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..eab37e5 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,23 @@ +root = true + +[*] +end_of_line = lf +insert_final_newline = true +indent_style = space +indent_size = 4 +trim_trailing_whitespace = true + +[*.{fs,fsx,fsi}] +max_line_length = 100 +fsharp_alternative_long_member_definitions = true +fsharp_multi_line_lambda_closing_newline = true +fsharp_multiline_bracket_style = aligned +fsharp_keep_max_number_of_blank_lines = 1 +fsharp_align_function_signature_to_indentation = true +fsharp_max_if_then_else_short_width = 0 + +fsharp_experimental_elmish = true +fsharp_record_multiline_formatter = number_of_items +fsharp_array_or_list_multiline_formatter = number_of_items +fsharp_max_record_number_of_items = 0 +fsharp_max_array_or_list_number_of_items = 0 diff --git a/Directory.Build.props b/Directory.Build.props index 86a0b40..6d0ad57 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,18 +1,30 @@ + + + true + true + + + + true + true + + + + + - MIT - README.md Chet Husk + MIT version;changelog;keepachangelog true + + https://github.com/ionide/KeepAChangelog ionide.png + README.md - - - - - - + + - \ No newline at end of file + diff --git a/Directory.Packages.props b/Directory.Packages.props new file mode 100644 index 0000000..506f00f --- /dev/null +++ b/Directory.Packages.props @@ -0,0 +1,22 @@ + + + true + true + false + + + + + + + + + + + + + + + + + diff --git a/Ionide.KeepAChangelog.sln b/Ionide.KeepAChangelog.sln index 87871f9..a63cfdc 100644 --- a/Ionide.KeepAChangelog.sln +++ b/Ionide.KeepAChangelog.sln @@ -5,13 +5,9 @@ VisualStudioVersion = 16.0.30114.105 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{31D4F8AF-532B-4DDF-BBA3-FD9B4C8FDA73}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.KeepAChangelog.Tasks", "src\Ionide.KeepAChangelog.Tasks\Ionide.KeepAChangelog.Tasks.fsproj", "{6CCAEBD2-9EE9-4540-AECF-02F0221B9C87}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.KeepAChangelog.Tasks", "src\Ionide.KeepAChangelog.Tasks.fsproj", "{6CCAEBD2-9EE9-4540-AECF-02F0221B9C87}" EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.KeepAChangelog", "src\Ionide.KeepAChangelog\Ionide.KeepAChangelog.fsproj", "{7C0C4ECD-27AF-47DD-904D-7952AC01A7A8}" -EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{B66528FD-2329-4D67-9C9A-3EF46C301815}" -EndProject -Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.KeepAChangelog.Test", "test\Ionide.KeepAChangelog.Test\Ionide.KeepAChangelog.Test.fsproj", "{349B0A8F-FBE0-4363-A950-ED4D6564560F}" +Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Ionide.KeepAChangelog.Tasks.Test", "tests\Ionide.KeepAChangelog.Tasks.Test.fsproj", "{6456526B-A0F6-4998-A57A-772A055DF8AD}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -26,18 +22,12 @@ Global {6CCAEBD2-9EE9-4540-AECF-02F0221B9C87}.Debug|Any CPU.Build.0 = Debug|Any CPU {6CCAEBD2-9EE9-4540-AECF-02F0221B9C87}.Release|Any CPU.ActiveCfg = Release|Any CPU {6CCAEBD2-9EE9-4540-AECF-02F0221B9C87}.Release|Any CPU.Build.0 = Release|Any CPU - {7C0C4ECD-27AF-47DD-904D-7952AC01A7A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {7C0C4ECD-27AF-47DD-904D-7952AC01A7A8}.Debug|Any CPU.Build.0 = Debug|Any CPU - {7C0C4ECD-27AF-47DD-904D-7952AC01A7A8}.Release|Any CPU.ActiveCfg = Release|Any CPU - {7C0C4ECD-27AF-47DD-904D-7952AC01A7A8}.Release|Any CPU.Build.0 = Release|Any CPU - {349B0A8F-FBE0-4363-A950-ED4D6564560F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {349B0A8F-FBE0-4363-A950-ED4D6564560F}.Debug|Any CPU.Build.0 = Debug|Any CPU - {349B0A8F-FBE0-4363-A950-ED4D6564560F}.Release|Any CPU.ActiveCfg = Release|Any CPU - {349B0A8F-FBE0-4363-A950-ED4D6564560F}.Release|Any CPU.Build.0 = Release|Any CPU + {6456526B-A0F6-4998-A57A-772A055DF8AD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6456526B-A0F6-4998-A57A-772A055DF8AD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6456526B-A0F6-4998-A57A-772A055DF8AD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6456526B-A0F6-4998-A57A-772A055DF8AD}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {6CCAEBD2-9EE9-4540-AECF-02F0221B9C87} = {31D4F8AF-532B-4DDF-BBA3-FD9B4C8FDA73} - {7C0C4ECD-27AF-47DD-904D-7952AC01A7A8} = {31D4F8AF-532B-4DDF-BBA3-FD9B4C8FDA73} - {349B0A8F-FBE0-4363-A950-ED4D6564560F} = {B66528FD-2329-4D67-9C9A-3EF46C301815} EndGlobalSection EndGlobal diff --git a/LICENSE b/LICENSE.txt similarity index 100% rename from LICENSE rename to LICENSE.txt diff --git a/global.json b/global.json index d769cd3..c19a2e0 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "6.0.400" + "version": "8.0.100", + "rollForward": "latestMinor" } -} \ No newline at end of file +} diff --git a/src/Ionide.KeepAChangelog.Tasks.fsproj b/src/Ionide.KeepAChangelog.Tasks.fsproj new file mode 100644 index 0000000..0813ad0 --- /dev/null +++ b/src/Ionide.KeepAChangelog.Tasks.fsproj @@ -0,0 +1,63 @@ + + + + net472;net8.0 + true + MSBuild Tasks and Targets that set your Assembly Version, Package Version, and Package Release Notes from your KeepAChangelog-compatible Changelogs. + + + $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage + NU5128;NU5100 + tasks + + + true + true + + en + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Ionide.KeepAChangelog.Tasks/Ionide.KeepAChangelog.Tasks.fsproj b/src/Ionide.KeepAChangelog.Tasks/Ionide.KeepAChangelog.Tasks.fsproj deleted file mode 100644 index 77607a0..0000000 --- a/src/Ionide.KeepAChangelog.Tasks/Ionide.KeepAChangelog.Tasks.fsproj +++ /dev/null @@ -1,78 +0,0 @@ - - - - net472;net6.0 - embedded - true - MSBuild Tasks and Targets that set your Assembly Version, Package Version, and Package Release Notes from your KeepAChangelog-compatible Changelogs. - - - $(TargetsForTfmSpecificBuildOutput);CopyProjectReferencesToPackage - NU5128;NU5100 - tasks - - - true - true - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/src/Ionide.KeepAChangelog.Tasks/Library.fs b/src/Ionide.KeepAChangelog.Tasks/Library.fs deleted file mode 100644 index 63f9382..0000000 --- a/src/Ionide.KeepAChangelog.Tasks/Library.fs +++ /dev/null @@ -1,97 +0,0 @@ -namespace KeepAChangelog.Tasks - -open Microsoft.Build.Utilities -open Microsoft.Build.Framework -open System.IO -open Ionide.KeepAChangelog -open Ionide.KeepAChangelog.Domain -open System.Linq - -module Util = - let mapReleaseInfo (version: SemVersion.SemanticVersion) (date: System.DateTime) (item: ITaskItem) : ITaskItem = - item.ItemSpec <- string version - item.SetMetadata("Date", date.ToString("yyyy-MM-dd")) - item - - let mapUnreleasedInfo (item: ITaskItem) : ITaskItem = - item.ItemSpec <- "Unreleased" - item - - let mapChangelogData (data: ChangelogData) (item: ITaskItem) : ITaskItem = - item.SetMetadata("Added", data.Added) - item.SetMetadata("Changed", data.Changed) - item.SetMetadata("Deprecated", data.Deprecated) - item.SetMetadata("Removed", data.Removed) - item.SetMetadata("Fixed", data.Fixed) - item.SetMetadata("Security", data.Security) - for (KeyValue(heading, lines)) in data.Custom do - item.SetMetadata(heading, lines) - item - -type ParseChangelogs() = - inherit Task() - - [] - member val ChangelogFile: string = null with get, set - - [] - member val UnreleasedChangelog: ITaskItem = null with get, set - - [] - member val CurrentReleaseChangelog: ITaskItem = null with get, set - - [] - member val AllReleasedChangelogs: ITaskItem [] = null with get, set - - [] - member val LatestReleaseNotes: string = null with get, set - - override this.Execute() : bool = - let file = this.ChangelogFile |> FileInfo - - if not file.Exists then - this.Log.LogError($"The file {file.FullName} could not be found.") - false - else - match Parser.parseChangeLog file with - | Ok changelogs -> - changelogs.Unreleased - |> Option.iter (fun unreleased -> - this.UnreleasedChangelog <- - TaskItem() - |> Util.mapChangelogData unreleased - |> Util.mapUnreleasedInfo) - - let sortedReleases = - // have to use LINQ here because List.sortBy* require IComparable, which - // semver doesn't implement - changelogs.Releases.OrderByDescending(fun (v, _, _) -> v) - - let items = - sortedReleases - |> Seq.map (fun (version, date, data) -> - TaskItem() - |> Util.mapReleaseInfo version date - |> fun d -> match data with Some data -> Util.mapChangelogData data d | None -> d - ) - |> Seq.toArray - - this.AllReleasedChangelogs <- items - this.CurrentReleaseChangelog <- items.FirstOrDefault() - - sortedReleases - |> Seq.tryHead - |> Option.iter (fun (version, date, data) -> - data - |> Option.iter (fun data -> - this.LatestReleaseNotes <- data.ToMarkdown()) - ) - - true - | Error (formatted, msg) -> - - this.Log.LogError( - $"Error parsing Changelog at {file.FullName}. The error occurred at {msg.Position}.{System.Environment.NewLine}{formatted}" - ) - - false diff --git a/src/Ionide.KeepAChangelog.Tasks/build/Ionide.KeepAChangelog.Tasks.props b/src/Ionide.KeepAChangelog.Tasks/build/Ionide.KeepAChangelog.Tasks.props deleted file mode 100644 index 4f5fed2..0000000 --- a/src/Ionide.KeepAChangelog.Tasks/build/Ionide.KeepAChangelog.Tasks.props +++ /dev/null @@ -1,5 +0,0 @@ - - - CHANGELOG.md - - \ No newline at end of file diff --git a/src/Ionide.KeepAChangelog.Tasks/build/Ionide.KeepAChangelog.Tasks.targets b/src/Ionide.KeepAChangelog.Tasks/build/Ionide.KeepAChangelog.Tasks.targets deleted file mode 100644 index 843bf9d..0000000 --- a/src/Ionide.KeepAChangelog.Tasks/build/Ionide.KeepAChangelog.Tasks.targets +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - SetVersionFromChangelog; - $(PrepareForBuildDependsOn) - - - - SetVersionFromChangelog - $(GetPackageVersionDependsOn); - - - <_Ionide_KeepAChangelog_Tasks_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472 - <_Ionide_KeepAChangelog_Tasks_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0 - - - - - - - - - - - - - - - - %(CurrentReleaseChangelog.Identity) - %(CurrentReleaseChangelog.Identity) - @(LatestReleaseNotes) - - - - - <_Parameter1>BuildDate - <_Parameter2>%(CurrentReleaseChangelog.Date) - - - - - \ No newline at end of file diff --git a/src/Ionide.KeepAChangelog.Tasks/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.props b/src/Ionide.KeepAChangelog.Tasks/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.props deleted file mode 100644 index 4f5fed2..0000000 --- a/src/Ionide.KeepAChangelog.Tasks/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.props +++ /dev/null @@ -1,5 +0,0 @@ - - - CHANGELOG.md - - \ No newline at end of file diff --git a/src/Ionide.KeepAChangelog.Tasks/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.targets b/src/Ionide.KeepAChangelog.Tasks/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.targets deleted file mode 100644 index 717ff92..0000000 --- a/src/Ionide.KeepAChangelog.Tasks/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.targets +++ /dev/null @@ -1,64 +0,0 @@ - - - - - SetVersionFromChangelog; - $(GenerateNuspecDependsOn) - - - SetVersionFromChangelog - $(GetPackageVersionDependsOn); - - - <_Ionide_KeepAChangelog_Tasks_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472 - <_Ionide_KeepAChangelog_Tasks_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net6.0 - - - - - - - - - - - - - - - - %(CurrentReleaseChangelog.Identity) - %(CurrentReleaseChangelog.Identity) - @(LatestReleaseNotes) - - - - - <_Parameter1>BuildDate - <_Parameter2>%(CurrentReleaseChangelog.Date) - - - - - \ No newline at end of file diff --git a/src/Ionide.KeepAChangelog/Ionide.KeepAChangelog.fsproj b/src/Ionide.KeepAChangelog/Ionide.KeepAChangelog.fsproj deleted file mode 100644 index daf816a..0000000 --- a/src/Ionide.KeepAChangelog/Ionide.KeepAChangelog.fsproj +++ /dev/null @@ -1,19 +0,0 @@ - - - - netstandard2.0 - true - embedded - true - A self-contained parser for the KeepAChangelog format. - - - - - - - - - - - diff --git a/src/Ionide.KeepAChangelog/Library.fs b/src/Ionide.KeepAChangelog/Library.fs deleted file mode 100644 index 420ae9c..0000000 --- a/src/Ionide.KeepAChangelog/Library.fs +++ /dev/null @@ -1,277 +0,0 @@ -namespace Ionide.KeepAChangelog - -module Domain = - open SemVersion - open System - - type ChangelogData = - { - Added: string - Changed: string - Deprecated: string - Removed: string - Fixed: string - Security: string - Custom: Map - } - static member Default = - { Added = String.Empty - Changed = String.Empty - Deprecated = String.Empty - Removed = String.Empty - Fixed = String.Empty - Security = String.Empty - Custom = Map.empty } - - member this.ToMarkdown () = - let section name (body: string) = - $"### {name}%s{Environment.NewLine}%s{Environment.NewLine}%s{body}" - - String.concat - Environment.NewLine - [ - section "Added" this.Added - section "Changed" this.Changed - section "Deprecated" this.Deprecated - section "Removed" this.Removed - section "Fixed" this.Fixed - section "Security" this.Security - for KeyValue(heading, lines) in this.Custom do - section heading lines - ] - - type Changelogs = - { Unreleased: ChangelogData option - Releases: (SemanticVersion * DateTime * ChangelogData option) list } - -module Parser = - - - open Domain - open FParsec - open FParsec.CharParsers - open FParsec.Primitives - open System.IO - open System.Collections.Generic - - type Parser<'t> = Parser<'t, unit> - - let pipe6 p1 p2 p3 p4 p5 p6 fn = - parse { - let! a = p1 - let! b = p2 - let! c = p3 - let! d = p4 - let! e = p5 - let! f = p6 - return fn a b c d e f - } - - let pipe7 p1 p2 p3 p4 p5 p6 p7 fn = - parse { - let! a = p1 - let! b = p2 - let! c = p3 - let! d = p4 - let! e = p5 - let! f = p6 - let! g = p7 - return fn a b c d e f g - } - - let skipTillStringOrEof str : Parser = - fun stream -> - let mutable found = false - - stream.SkipCharsOrNewlinesUntilString(str, System.Int32.MaxValue, &found) - |> ignore - - Reply(()) - - let pBullet: Parser = (attempt (pchar '-') <|> pchar '*') "bullet" - - let pEntry: Parser = - let bullet = attempt (pBullet .>> spaces1) - - let content = - // we need to parse all of this line, sure - // but we also need to keep parsing next lines until - // * we find a bullet, or - // * we get an empty line - let firstLine = FParsec.CharParsers.restOfLine true - - let followingLine = - nextCharSatisfiesNot (fun c -> c = '\n' || c = '-' || c = '*') - >>. spaces1 - >>. FParsec.CharParsers.restOfLine true - - let rest = opt (many1 (attempt followingLine)) - - pipe2 firstLine rest (fun f rest -> - match rest with - | None -> f - | Some parts -> String.concat " " (f :: parts)) - "line item" - - pipe2 bullet content (fun bullet text -> $"{bullet} {text}") - - let pSectionBody sectionName : Parser = - let nextHeader = (newline >>. regex @"[#]{1,3}\s\S" |>> ignore) - let endOfSection = choice [ eof; nextHeader ] - manyTill anyChar (lookAhead endOfSection) - |>> System.String.Concat - $"{sectionName} section body" - - let pCustomSection: Parser = - let sectionName = - skipString "###" - >>. spaces1 - >>. restOfLine true // TODO: maybe not the whole line? - $"custom section header" - sectionName - .>> attempt (opt newline) - .>>. (pSectionBody sectionName) - .>> attempt (opt newline) - - let pSection sectionName : Parser = - ((skipString "###" - >>. spaces1 - >>. skipString sectionName) - $"{sectionName} section header") - >>. many1 newline - >>. pSectionBody sectionName - .>> attempt (opt newline) - - let pAdded = pSection "Added" - let pChanged = pSection "Changed" - let pRemoved = pSection "Removed" - let pDeprecated = pSection "Deprecated" - let pFixed = pSection "Fixed" - let pSecurity = pSection "Security" - let pOrEmptyList p = opt (attempt p) - let pSectionLessItems = - many1 pEntry - .>> attempt (opt newline) - - let pSections: Parser ChangelogData> = - choice [ - attempt (pAdded |>> fun x data -> { data with Added = x }) - attempt (pChanged |>> fun x data -> { data with Changed = x }) - attempt (pRemoved |>> fun x data -> { data with Removed = x }) - attempt (pDeprecated |>> fun x data -> { data with Deprecated = x }) - attempt (pFixed |>> fun x data -> { data with Fixed = x }) - attempt (pSecurity |>> fun x data -> { data with Security = x }) - attempt (many1 pCustomSection |>> fun x data -> { data with Custom = Map.ofList x }) - ] - - let pData: Parser = - many1 pSections - |>> List.fold (fun x f -> f x) ChangelogData.Default - - let pNonStructuredData : Parser = - let nextHeader = (newline >>. regex @"[#]{1,2}\s\S" |>> ignore) - let endOfSection = choice [ eof; nextHeader ] - (manyTill anyChar (lookAhead endOfSection) .>> attempt (opt newline)) - |>> (fun _content -> ChangelogData.Default) - "release body" - - let pHeader: Parser = - (skipString "# Changelog" >>. skipNewline - .>> skipTillStringOrEof "##") - "Changelog header" - - let mdUrl inner = - let linkText = between (pchar '[') (pchar ']') inner - - let linkTarget = between (pchar '(') (pchar ')') (skipMany1Till anyChar (pchar ')')) - - linkText .>> opt linkTarget - - let pUnreleased: Parser = - let unreleased = skipString "Unreleased" - let name = attempt ( - skipString "##" - >>. spaces1 - >>. (mdUrl unreleased <|> unreleased) - .>> skipRestOfLine true - "Unreleased label" - ) - - name >>. opt (many newline) >>. opt pData - "Unreleased version section" - - let validSemverChars = - [| for c in '0' .. '9' -> c - for c in 'A' .. 'Z' -> c - for c in 'a' .. 'z' -> c - yield '-' - yield '.' - yield '+' |] - |> Set.ofArray - - let pSemver: Parser<_> = - many1Chars (satisfy validSemverChars.Contains) - |>> fun text -> SemVersion.SemanticVersion.Parse text - - let pDate: Parser<_> = - let pYear = - pint32 - |> attempt - - - let pMonth = - pint32 - |> attempt - - let pDay = - pint32 - |> attempt - - let ymdDashes = - let dash = pchar '-' - pipe5 pYear dash pMonth dash pDay (fun y _ m _ d -> System.DateTime(y, m, d)) - - - let dmyDots = - let dot = pchar '.' - pipe5 pDay dot pMonth dot pYear (fun d _ m _ y -> System.DateTime(y, m, d)) - - attempt dmyDots <|> ymdDashes - - - let pVersion = mdUrl pSemver <|> pSemver - - let pRelease: Parser = - let vPart = skipString "##" >>. spaces1 >>. pVersion - let middle = spaces1 .>> pchar '-' .>> spaces1 - let date = pDate .>> skipRestOfLine true - let content = choice [ pData; pNonStructuredData ] - - pipe5 vPart middle date (opt (many newline)) (opt content) (fun v _ date _ data -> v, date, data) - - let pChangeLogs: Parser = - let unreleased = - pUnreleased - |>> fun unreleased -> - match unreleased with - | None -> None - | Some u when u = ChangelogData.Default -> None - | Some unreleased -> Some unreleased - pipe3 - pHeader - (attempt (opt unreleased)) - (attempt (many pRelease)) - (fun _header unreleased releases -> - { Unreleased = defaultArg unreleased None - Releases = releases }) - - let parseChangeLog (file: FileInfo) = - match - runParserOnFile - pChangeLogs - () - file.FullName - (System.Text.UTF8Encoding(encoderShouldEmitUTF8Identifier = false)) - with - | ParserResult.Success (result, _, pos) -> Result.Ok result - | ParserResult.Failure (msg, structuredError, pos) -> Result.Error(msg, structuredError) diff --git a/src/Library.fs b/src/Library.fs new file mode 100644 index 0000000..9169259 --- /dev/null +++ b/src/Library.fs @@ -0,0 +1,169 @@ +namespace Ionide.KeepAChangelog.Tasks + +open System +open System.Globalization +open System.Runtime.CompilerServices +open System.Text +open Microsoft.Build.Utilities +open Microsoft.Build.Framework +open System.IO +open KeepAChangelogParser +open KeepAChangelogParser.Models +open KeepAChangelog +open FsToolkit.ErrorHandling +open Semver + +module Result = + + let toBool = + function + | Ok _ -> true + | Error _ -> false + +type ChangelogExtensions = + [] + static member inline ToDateTime(section: ChangelogSection) = + DateTime.ParseExact(section.MarkdownDate, "yyyy-MM-dd", CultureInfo.InvariantCulture) + + [] + static member ToTaskItemMetadata(sections: ChangelogSubSectionCollection) = + sections + |> Seq.map (fun section -> + (string section.Type, + section.ItemCollection + |> Seq.map _.MarkdownText + |> String.concat Environment.NewLine)) + + [] + static member ToTaskItem(unreleased: ChangelogSectionUnreleased) = + let taskItem = TaskItem("unreleased") + + for (key, value) in unreleased.SubSectionCollection.ToTaskItemMetadata() do + taskItem.SetMetadata(key, value) + + taskItem + + [] + static member Unwrapped(sections: ChangelogSectionCollection) = + sections + |> Seq.choose (fun section -> + match SemVersion.TryParse section.MarkdownVersion with + | false, _ -> None + | true, version -> + Some + {| version = version + date = section.ToDateTime() + collection = section.SubSectionCollection |}) + + [] + static member ToMarkdown(subsections: ChangelogSubSectionCollection) = + let builder = StringBuilder() + + subsections + |> Seq.fold + (fun (builder: StringBuilder) subsection -> + let state = builder.AppendLine $"### {subsection.Type}" + + subsection.ItemCollection + |> Seq.fold (fun (builder: StringBuilder) line -> builder.AppendLine line.MarkdownText) state) + builder + |> _.ToString() + +type ParseChangeLogs() = + inherit Task() + + [] + member val ChangelogFile: string = null with get, set + + [] + member val UnreleasedChangelog: ITaskItem = null with get, set + + [] + member val CurrentReleaseChangelog: ITaskItem = null with get, set + + [] + member val AllReleasedChangelogs: ITaskItem[] = null with get, set + + [] + member val LatestReleaseNotes: string = null with get, set + + override this.Execute() : bool = + let file = this.ChangelogFile |> FileInfo + + // Using result CE to make code easier to read by avoiding nested if statements + result { + do! this.CheckFileExists file + let! changelog = this.ParseChangelog file + + do! this.ReadUnreleasedSection changelog + do! this.ProcessReleases changelog + + return true + } + |> Result.toBool + + member this.CheckFileExists(fileInfo: FileInfo) = + if fileInfo.Exists then + Ok() + else + this.LogError(Log.changelogFileNotFound fileInfo.FullName) + Error() + + member this.ParseChangelog(fileInfo: FileInfo) : Result = + let changelogContent = File.ReadAllText(fileInfo.FullName) + let parserResult = ChangelogParser().Parse(changelogContent) + + if parserResult.IsSuccess then + Ok parserResult.Value + else + this.LogError(Log.invalidChangelog fileInfo.FullName parserResult.Error) + Error() + + member this.ReadUnreleasedSection(changelog: Changelog) = + match changelog.SectionUnreleased with + | null -> Ok() + | unreleased -> + this.UnreleasedChangelog <- unreleased.ToTaskItem() + Ok() + + member this.ProcessReleases(changelog: Changelog) = + let releases = + changelog.SectionCollection.Unwrapped() + |> Seq.sortByDescending _.version + |> Seq.toArray + + let latestRelease = releases |> (fun x -> x[0]) + + let mapped = + releases + |> Array.map (fun x -> + let taskItem = TaskItem(x.version.ToString()) + taskItem.SetMetadata("Date", x.date.ToString("yyyy-MM-dd")) + + for (key, value) in x.collection.ToTaskItemMetadata() do + taskItem.SetMetadata(key, value) + + taskItem :> ITaskItem) + + this.CurrentReleaseChangelog <- mapped[0] + this.AllReleasedChangelogs <- mapped + this.LatestReleaseNotes <- latestRelease.collection.ToMarkdown() + Ok() + + /// + /// Helper method to log an error with the given log data. + /// + /// + member this.LogError(logData: Log.LogData) = + this.Log.LogError( + "CHANGELOG", + logData.ErrorCode, + logData.HelpKeyword, + this.BuildEngine.ProjectFileOfTaskNode, + this.BuildEngine.LineNumberOfTaskNode, + this.BuildEngine.ColumnNumberOfTaskNode, + this.BuildEngine.LineNumberOfTaskNode, + this.BuildEngine.ColumnNumberOfTaskNode, + logData.Message, + logData.MessageArgs + ) diff --git a/src/Log.fs b/src/Log.fs new file mode 100644 index 0000000..0a12007 --- /dev/null +++ b/src/Log.fs @@ -0,0 +1,38 @@ +module KeepAChangelog.Log + +(* + Log module allowing to define log data for different errors. + + It makes it easier to keep track of the error codes, because everything is in one place. +*) + +type LogData = + { + ErrorCode: string + HelpKeyword: string + Message: string + MessageArgs: obj array + } + +let changelogFileNotFound (filePath: string) = + { + ErrorCode = "IKC0001" + HelpKeyword = "Missing Changelog file" + Message = "The Changelog file {0} was not found." + MessageArgs = + [| + box filePath + |] + } + +let invalidChangelog (filePath: string) (error: string) = + { + ErrorCode = "IKC0002" + HelpKeyword = "Invalid Changelog file" + Message = "The Changelog file {0} is invalid. The error was: {1}" + MessageArgs = + [| + box filePath + box error + |] + } diff --git a/src/build/Core.targets b/src/build/Core.targets new file mode 100644 index 0000000..17c48ac --- /dev/null +++ b/src/build/Core.targets @@ -0,0 +1,41 @@ + + + <_Ionide_KeepAChangelog_Tasks_TFM Condition=" '$(MSBuildRuntimeType)' != 'Core' ">net472 + <_Ionide_KeepAChangelog_Tasks_TFM Condition=" '$(MSBuildRuntimeType)' == 'Core' ">net8.0 + + + + + + + + + + + + + + + + + + + + + + + + + %(CurrentReleaseChangelog.Identity) + %(CurrentReleaseChangelog.Identity) + @(LatestReleaseNotes) + + + + + <_Parameter1>BuildDate + <_Parameter2>%(CurrentReleaseChangelog.Date) + + + + diff --git a/src/build/Ionide.KeepAChangelog.Tasks.targets b/src/build/Ionide.KeepAChangelog.Tasks.targets new file mode 100644 index 0000000..c400ee4 --- /dev/null +++ b/src/build/Ionide.KeepAChangelog.Tasks.targets @@ -0,0 +1,17 @@ + + + + + SetVersionFromChangelog; + $(PrepareForBuildDependsOn) + + + + SetVersionFromChangelog + $(GetPackageVersionDependsOn); + + + + + diff --git a/src/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.targets b/src/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.targets new file mode 100644 index 0000000..867f78e --- /dev/null +++ b/src/buildMultiTargeting/Ionide.KeepAChangelog.Tasks.targets @@ -0,0 +1,19 @@ + + + + + SetVersionFromChangelog; + $(GenerateNuspecDependsOn) + + + SetVersionFromChangelog + $(GetPackageVersionDependsOn); + + + + + + diff --git a/src/packages.lock.json b/src/packages.lock.json new file mode 100644 index 0000000..039c73a --- /dev/null +++ b/src/packages.lock.json @@ -0,0 +1,297 @@ +{ + "version": 2, + "dependencies": { + ".NETFramework,Version=v4.7.2": { + "DotNet.ReproducibleBuilds": { + "type": "Direct", + "requested": "[1.2.25, )", + "resolved": "1.2.25", + "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" + }, + "FSharp.Core": { + "type": "Direct", + "requested": "[7.0.300, )", + "resolved": "7.0.300", + "contentHash": "8vvItREJ1l5lcp3vBCSJ1mFevVAhR48I34DuF/EoUa7o1KlFpQpagyuZkVYMAsHPIjdp47ZxM9sI4eqeXaeWkA==" + }, + "FsToolkit.ErrorHandling": { + "type": "Direct", + "requested": "[4.17.0, )", + "resolved": "4.17.0", + "contentHash": "eW37u0mip2bCjXK+z5VJUN1VdRz2QZYVZN1xLwKagrw4IS9jjQSR8P2OW5msE82AShpW2fM9ySEVxtZfoPBTiA==", + "dependencies": { + "FSharp.Core": "4.7.2" + } + }, + "KeepAChangeLogParser": { + "type": "Direct", + "requested": "[1.2.4, )", + "resolved": "1.2.4", + "contentHash": "895vwfwdD52hTtmwZDQgXLlpbMia+vz+ZpkLCs7idBR+XD3mECwRULiBch4+1+7LCCW0FZMxKoG4GOD4VkqO9Q==", + "dependencies": { + "CSharpFunctionalExtensions": "2.42.5", + "Roslynator.Analyzers": "4.12.4" + } + }, + "Microsoft.Build.Utilities.Core": { + "type": "Direct", + "requested": "[17.8.3, )", + "resolved": "17.8.3", + "contentHash": "ex8Sjx02q0FmForLRFItB82sJx5s2JRWIpJgYDW1g7xLUoFlKLoNp67UwS5xN8YcYBkT7vRxXeYx/dQ96KaWtg==", + "dependencies": { + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.IO.Redist": "6.0.0", + "Microsoft.NET.StringTools": "17.8.3", + "Microsoft.VisualStudio.Setup.Configuration.Interop": "3.2.2146", + "System.Collections.Immutable": "7.0.0", + "System.Configuration.ConfigurationManager": "7.0.0", + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "Semver": { + "type": "Direct", + "requested": "[2.3.0, )", + "resolved": "2.3.0", + "contentHash": "4vYo1zqn6pJ1YrhjuhuOSbIIm0CpM47grbpTJ5ABjOlfGt/EhMEM9ed4MRK5Jr6gVnntWDqOUzGeUJp68PZGjw==" + }, + "CSharpFunctionalExtensions": { + "type": "Transitive", + "resolved": "2.42.5", + "contentHash": "Do9Q7Nyfx2G4HRt96aB6yoP5Z9ic3jqBHLrf35tULzsWlFoBlkQZT62aXCiv7UmVmF56OUR+a3JoSSs140hfHQ==" + }, + "Microsoft.Build.Framework": { + "type": "Transitive", + "resolved": "17.8.3", + "contentHash": "NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==", + "dependencies": { + "Microsoft.VisualStudio.Setup.Configuration.Interop": "3.2.2146", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "Microsoft.IO.Redist": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "uxXZ8pAcYtIJm8iqu/0e+CkM/VSwfgbHpnCDu7s8+gn/VUD5R6PxH3RGZFPaHgTisrlwD+BIyL5TqG6qwuZtOQ==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Memory": "4.5.4" + } + }, + "Microsoft.NET.StringTools": { + "type": "Transitive", + "resolved": "17.8.3", + "contentHash": "y6DiuacjlIfXH3XVQG5htf+4oheinZAo7sHbITB3z7yCXQec48f9ZhGSXkr+xn1bfl73Yc3ZQEW2peJ5X68AvQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "Microsoft.VisualStudio.Setup.Configuration.Interop": { + "type": "Transitive", + "resolved": "3.2.2146", + "contentHash": "gMq8uGy8zTIp0kQGTI45buZC3JOStGJyjGD8gksskk83aQISW65IESErLE/WDT7Bdy+QWbdUi7QyO1LEzUSOFA==" + }, + "Roslynator.Analyzers": { + "type": "Transitive", + "resolved": "4.12.4", + "contentHash": "isl8hAh7yFNjyBEC4YlTSi+xGBblqBUC/2MCMmnBPwuXPewb7XYnMRzT3vXbP/gOGwT8hZUOy1g/aRH3lAF/NQ==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "WvRUdlL1lB0dTRZSs5XcQOd5q9MYNk90GkbmRmiCvRHThWiojkpGqWdmEDJdXyHbxG/BhE5hmVbMfRLXW9FJVA==", + "dependencies": { + "System.Security.Permissions": "7.0.0" + } + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==", + "dependencies": { + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Vmp0iRmCEno9BWiskOW5pxJ3d9n+jUqKxvX4GhLwFhnQaySZmBN2FuC0N5gjFHgyFMUjC5sfIJ8KZfoJwkcMmA==", + "dependencies": { + "System.Security.AccessControl": "6.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + } + }, + "net8.0": { + "DotNet.ReproducibleBuilds": { + "type": "Direct", + "requested": "[1.2.25, )", + "resolved": "1.2.25", + "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" + }, + "FSharp.Core": { + "type": "Direct", + "requested": "[7.0.300, )", + "resolved": "7.0.300", + "contentHash": "8vvItREJ1l5lcp3vBCSJ1mFevVAhR48I34DuF/EoUa7o1KlFpQpagyuZkVYMAsHPIjdp47ZxM9sI4eqeXaeWkA==" + }, + "FsToolkit.ErrorHandling": { + "type": "Direct", + "requested": "[4.17.0, )", + "resolved": "4.17.0", + "contentHash": "eW37u0mip2bCjXK+z5VJUN1VdRz2QZYVZN1xLwKagrw4IS9jjQSR8P2OW5msE82AShpW2fM9ySEVxtZfoPBTiA==", + "dependencies": { + "FSharp.Core": "7.0.300" + } + }, + "KeepAChangeLogParser": { + "type": "Direct", + "requested": "[1.2.4, )", + "resolved": "1.2.4", + "contentHash": "895vwfwdD52hTtmwZDQgXLlpbMia+vz+ZpkLCs7idBR+XD3mECwRULiBch4+1+7LCCW0FZMxKoG4GOD4VkqO9Q==", + "dependencies": { + "CSharpFunctionalExtensions": "2.42.5", + "Roslynator.Analyzers": "4.12.4" + } + }, + "Microsoft.Build.Utilities.Core": { + "type": "Direct", + "requested": "[17.8.3, )", + "resolved": "17.8.3", + "contentHash": "ex8Sjx02q0FmForLRFItB82sJx5s2JRWIpJgYDW1g7xLUoFlKLoNp67UwS5xN8YcYBkT7vRxXeYx/dQ96KaWtg==", + "dependencies": { + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.NET.StringTools": "17.8.3", + "Microsoft.VisualStudio.Setup.Configuration.Interop": "3.2.2146", + "System.Collections.Immutable": "7.0.0", + "System.Configuration.ConfigurationManager": "7.0.0" + } + }, + "Semver": { + "type": "Direct", + "requested": "[2.3.0, )", + "resolved": "2.3.0", + "contentHash": "4vYo1zqn6pJ1YrhjuhuOSbIIm0CpM47grbpTJ5ABjOlfGt/EhMEM9ed4MRK5Jr6gVnntWDqOUzGeUJp68PZGjw==" + }, + "CSharpFunctionalExtensions": { + "type": "Transitive", + "resolved": "2.42.5", + "contentHash": "Do9Q7Nyfx2G4HRt96aB6yoP5Z9ic3jqBHLrf35tULzsWlFoBlkQZT62aXCiv7UmVmF56OUR+a3JoSSs140hfHQ==" + }, + "Microsoft.Build.Framework": { + "type": "Transitive", + "resolved": "17.8.3", + "contentHash": "NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==" + }, + "Microsoft.NET.StringTools": { + "type": "Transitive", + "resolved": "17.8.3", + "contentHash": "y6DiuacjlIfXH3XVQG5htf+4oheinZAo7sHbITB3z7yCXQec48f9ZhGSXkr+xn1bfl73Yc3ZQEW2peJ5X68AvQ==" + }, + "Microsoft.VisualStudio.Setup.Configuration.Interop": { + "type": "Transitive", + "resolved": "3.2.2146", + "contentHash": "gMq8uGy8zTIp0kQGTI45buZC3JOStGJyjGD8gksskk83aQISW65IESErLE/WDT7Bdy+QWbdUi7QyO1LEzUSOFA==" + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" + }, + "Roslynator.Analyzers": { + "type": "Transitive", + "resolved": "4.12.4", + "contentHash": "isl8hAh7yFNjyBEC4YlTSi+xGBblqBUC/2MCMmnBPwuXPewb7XYnMRzT3vXbP/gOGwT8hZUOy1g/aRH3lAF/NQ==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==" + }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "WvRUdlL1lB0dTRZSs5XcQOd5q9MYNk90GkbmRmiCvRHThWiojkpGqWdmEDJdXyHbxG/BhE5hmVbMfRLXW9FJVA==", + "dependencies": { + "System.Diagnostics.EventLog": "7.0.0", + "System.Security.Cryptography.ProtectedData": "7.0.0", + "System.Security.Permissions": "7.0.0" + } + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "eUDP47obqQm3SFJfP6z+Fx2nJ4KKTQbXB4Q9Uesnzw9SbYdhjyoGXuvDn/gEmFY6N5Z3bFFbpAQGA7m6hrYJCw==" + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "7.0.0" + } + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xSPiLNlHT6wAHtugASbKAJwV5GVqQK351crnILAucUioFqqieDN79evO1rku1ckt/GfjIn+b17UaSskoY03JuA==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Vmp0iRmCEno9BWiskOW5pxJ3d9n+jUqKxvX4GhLwFhnQaySZmBN2FuC0N5gjFHgyFMUjC5sfIJ8KZfoJwkcMmA==", + "dependencies": { + "System.Windows.Extensions": "7.0.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "bR4qdCmssMMbo9Fatci49An5B1UaVJZHKNq70PRgzoLYIlitb8Tj7ns/Xt5Pz1CkERiTjcVBDU2y1AVrPBYkaw==", + "dependencies": { + "System.Drawing.Common": "7.0.0" + } + } + } + } +} \ No newline at end of file diff --git a/test/Ionide.KeepAChangelog.Test/Ionide.KeepAChangelog.Test.fsproj b/test/Ionide.KeepAChangelog.Test/Ionide.KeepAChangelog.Test.fsproj deleted file mode 100644 index 2b2316a..0000000 --- a/test/Ionide.KeepAChangelog.Test/Ionide.KeepAChangelog.Test.fsproj +++ /dev/null @@ -1,24 +0,0 @@ - - - Exe - net6.0 - CHANGELOG.md - embedded - false - true - false - - - - - - - - - - - - - - - diff --git a/test/Ionide.KeepAChangelog.Test/Program.fs b/test/Ionide.KeepAChangelog.Test/Program.fs deleted file mode 100644 index 01b4e45..0000000 --- a/test/Ionide.KeepAChangelog.Test/Program.fs +++ /dev/null @@ -1,408 +0,0 @@ -open Ionide.KeepAChangelog - -open System -open SemVersion -open Expecto -open Ionide.KeepAChangelog.Domain - -let normalizeNewline (v:string) = v.Replace("\r", "") - -let singleRelease = - normalizeNewline """## [1.0.0] - 2017-06-20 -### Added -- A - -### Changed -- B - -### Removed -- C -""" - -let singleReleaseExpected = - (SemanticVersion.Parse "1.0.0", DateTime(2017, 06, 20), Some { - ChangelogData.Default with - Added = "- A\n" - Changed = "- B\n" - Removed = "- C\n" - }) - -let keepAChangelog = - normalizeNewline """# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [1.0.0] - 2017-06-20 -### Added -- A - -### Changed -- B - -### Removed -- C - -## [0.3.0] - 2015-12-03 -### Added -- A -- B -- C - -""" - -let keepAChangelogExpected: Changelogs = - { - Unreleased = None - Releases = [ - singleReleaseExpected - SemanticVersion.Parse("0.3.0"), - DateTime(2015, 12, 03), - Some { - ChangelogData.Default with - Added = "- A\n- B\n- C\n\n" - } - ] - } - -let header = - normalizeNewline """# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -""" - -let emptyUnreleased = - normalizeNewline """## [Unreleased] - -""" - -let headerAndUnreleased = header + emptyUnreleased - -let headerAndUnreleasedAndRelease = header + emptyUnreleased + singleRelease -let headerAndUnreleasedAndReleaseExpected = None, singleReleaseExpected - -let sample1Release = normalizeNewline """## [0.3.1] - 8.1.2022 - -### Added - -- Add XmlDocs to the generated package - -""" - -let sample1ReleaseExpected = - SemanticVersion.Parse "0.3.1", DateTime(2022, 1, 8), Some { ChangelogData.Default with Added = "- Add XmlDocs to the generated package\n\n" } - -let sample = normalizeNewline """# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [0.3.1] - 8.1.2022 - -### Added - -* Add XmlDocs to the generated package - -## [0.3.0] - 23.11.2021 - -### Added - -* Expose client `CodeAction` caps as CodeActionClientCapabilities. (by @razzmatazz) -* Map CodeAction.IsPreferred & CodeAction.Disabled props. (by @razzmatazz) - -## [0.2.0] - 17.11.2021 - -### Added - -* Add support for `codeAction/resolve` (by @razzmatazz) - -## [0.1.1] - 15.11.2021 - -### Added - -* Initial implementation -""" - -let sampleExpected: Changelogs = { - Unreleased = None - Releases = [ - SemanticVersion.Parse "0.3.1", - DateTime(2022, 1, 8), - Some { ChangelogData.Default with Added = "* Add XmlDocs to the generated package\n" } - - SemanticVersion.Parse "0.3.0", - DateTime(2021, 11, 23), - Some { - ChangelogData.Default with - Added = - normalizeNewline - """* Expose client `CodeAction` caps as CodeActionClientCapabilities. (by @razzmatazz) -* Map CodeAction.IsPreferred & CodeAction.Disabled props. (by @razzmatazz) -""" } - SemanticVersion.Parse "0.2.0", - DateTime(2021, 11, 17), - Some { ChangelogData.Default with Added = "* Add support for `codeAction/resolve` (by @razzmatazz)\n" } - - SemanticVersion.Parse "0.1.1", - DateTime(2021, 11, 15), - Some { ChangelogData.Default with Added = "* Initial implementation\n" } - ] -} - -open FParsec -open FParsec.Primitives - -let runSuccess label p text expected = - test $"parsing {label}" { - - match FParsec.CharParsers.run p text with - | FParsec.CharParsers.Success (r, _, _) -> - Expect.equal r expected "Should have produced expected value" - | FParsec.CharParsers.Failure (m, _, _) -> - failwithf "%A" m - } - -let runSuccessNormalized label (p: Parser) text (expected:string) = - test $"parsing {label}" { - match FParsec.CharParsers.run p text with - | FParsec.CharParsers.Success (r, _, _) -> - let normalizedR = r.Replace("\r", "") - let normalizedExpected = expected.Replace("\r", "") - Expect.equal normalizedR normalizedExpected "Should have produced expected value" - | FParsec.CharParsers.Failure (m, _, _) -> - failwithf "%A" m - } - -let parsingExamples = testList "parsing examples" [ - runSuccess "line entry" Parser.pEntry "- A" "- A" - runSuccess "header" Parser.pHeader header () - runSuccess "unreleased" Parser.pUnreleased emptyUnreleased None - runSuccess "header and unreleased" (Parser.pHeader >>. Parser.pUnreleased) headerAndUnreleased None - runSuccess "release" Parser.pRelease singleRelease singleReleaseExpected - runSuccess "sample 1 release" Parser.pRelease sample1Release sample1ReleaseExpected - runSuccess - "header and unreleased and released" - (Parser.pHeader >>. Parser.pUnreleased - .>>. Parser.pRelease) - headerAndUnreleasedAndRelease - headerAndUnreleasedAndReleaseExpected - runSuccess "keepachangelog" Parser.pChangeLogs keepAChangelog keepAChangelogExpected - runSuccess "lsp changelog" Parser.pChangeLogs sample sampleExpected -] - -let changelogDataTest = - test "Transform ChangelogData to Markdown" { - let changelogData = - { - Added = "* Added line 1\n* Added line 2\n" - Changed = "* Changed line 1\n* Changed line 2\n" - Deprecated = "* Deprecated line 1\n* Deprecated line 2\n" - Removed = "* Removed line 1\n* Removed line 2\n" - Fixed = "* Fixed line 1\n* Fixed line 2\n" - Security = "* Security line 1\n* Security line 2\n" - Custom = - [ - "CustomHeaderA", "* Custom line 1\n* Custom line 2\n" - "CustomHeaderB", "* Custom line 3\n* Custom line 4\n" - ] - |> Map.ofList - } - - let expected = - normalizeNewline """### Added - -* Added line 1 -* Added line 2 - -### Changed - -* Changed line 1 -* Changed line 2 - -### Deprecated - -* Deprecated line 1 -* Deprecated line 2 - -### Removed - -* Removed line 1 -* Removed line 2 - -### Fixed - -* Fixed line 1 -* Fixed line 2 - -### Security - -* Security line 1 -* Security line 2 - -### CustomHeaderA - -* Custom line 1 -* Custom line 2 - -### CustomHeaderB - -* Custom line 3 -* Custom line 4 -""" - - Expect.equal (normalizeNewline (changelogData.ToMarkdown())) expected "Should have produced expected value" -} - -let FableSample = """# Changelog -All notable changes to this project will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## Unreleased - -### Fixed - -#### Python - -* Fix #3617: Fix comparaison between list option when one is None -* Fix #3615: Fix remove from dictionary with tuple as key -* Fix #3598: Using obj () now generated an empty dict instead of None -* Fix #3597: Do not translate .toString methods to str -* Fix #3610: Cleanup Python regex handling -* Fix #3628: System.DateTime.Substract not correctly transpiled - -## 4.6.0 - 2023-11-27 - -### Changed - -#### All - -* Updated .NET metadata to 8.0.100 (by @ncave) - -### Added - -#### All - -* Fix #3584: Unit type compiles to undeclared variable (by @ncave) - -#### Python - -* Support `DateTime(..., DateTimeKind.Utc).ToString("O")` (by @MangelMaxime) - -#### Rust - -* Added `Guid.TryParse`, `Guid.ToByteArray` (by @ncave) - -### Fixed - -#### Python - -* Fixed char to string type regression with binary operator (by @dbrattli) -* Fix `DateTime(..., DateTimeKind.Local).ToString("O")` (by @MangelMaxime) -* Fix calling `value.ToString(CultureInfo.InvariantCulture)` (by @MangelMaxime) -* Fix #3605: Fix record equality comparison to works with optional fields (by @MangelMaxime and @dbrattli) -* PR #3608: Rewrite `time_span.py` allowing for better precision by using a number representation intead of native `timedelta`. (by @MangelMaxime) -""" - -let FableSampleExpected :Changelogs = { - Unreleased = Some { - ChangelogData.Default with - Fixed = - normalizeNewline - """#### Python - -* Fix #3617: Fix comparaison between list option when one is None -* Fix #3615: Fix remove from dictionary with tuple as key -* Fix #3598: Using obj () now generated an empty dict instead of None -* Fix #3597: Do not translate .toString methods to str -* Fix #3610: Cleanup Python regex handling -* Fix #3628: System.DateTime.Substract not correctly transpiled -""" - } - Releases = [ - SemanticVersion.Parse "4.6.0", - DateTime(2023, 11, 27), - Some { - ChangelogData.Default with - Changed = - normalizeNewline """#### All - -* Updated .NET metadata to 8.0.100 (by @ncave) -""" - Added = - normalizeNewline """#### All - -* Fix #3584: Unit type compiles to undeclared variable (by @ncave) - -#### Python - -* Support `DateTime(..., DateTimeKind.Utc).ToString("O")` (by @MangelMaxime) - -#### Rust - -* Added `Guid.TryParse`, `Guid.ToByteArray` (by @ncave) -""" - Fixed = - normalizeNewline """#### Python - -* Fixed char to string type regression with binary operator (by @dbrattli) -* Fix `DateTime(..., DateTimeKind.Local).ToString("O")` (by @MangelMaxime) -* Fix calling `value.ToString(CultureInfo.InvariantCulture)` (by @MangelMaxime) -* Fix #3605: Fix record equality comparison to works with optional fields (by @MangelMaxime and @dbrattli) -* PR #3608: Rewrite `time_span.py` allowing for better precision by using a number representation intead of native `timedelta`. (by @MangelMaxime) -""" - } - ] -} - -let SectionLessSample = normalizeNewlines """# Changelog - -## 4.2.1 - 2023-09-29 - -* Fix package to include Fable libraries folders - -## 4.2.0 - 2023-09-29 - -* Fix #3480: Function decorated with `[]` without arguments provided should take an empty object -* Fix #3528: Consider functions hidden by a signature file as private (@nojaf) -* Improve error message when Fable doesn't find the `fable-library` folder. - - This is especially useful when working on Fable itself, and should save time to others. - Each time I got this is error, I needed several minutes to remember the cause of it. -""" - -let SectionLessSampleExpected: Changelogs = { - Unreleased = None - Releases = [ - SemanticVersion.Parse "4.2.1", - DateTime(2023, 9, 29), - Some ChangelogData.Default - SemanticVersion.Parse "4.2.0", - DateTime(2023, 9, 29), - Some ChangelogData.Default - ] -} - -let fableTests = testList "Fable" [ - runSuccess "Multiple languages" Parser.pChangeLogs FableSample FableSampleExpected - runSuccess "SectionLess items" Parser.pChangeLogs SectionLessSample SectionLessSampleExpected -] - -[] -let tests = testList "All" [ - parsingExamples - changelogDataTest - fableTests -] - -[] -let main argv = - runTestsWithCLIArgs Seq.empty argv tests diff --git a/tests/IntegrationTests.fs b/tests/IntegrationTests.fs new file mode 100644 index 0000000..6852950 --- /dev/null +++ b/tests/IntegrationTests.fs @@ -0,0 +1,27 @@ +module Tests.IntegrationTests + +open Tests.Setup +open Moq +open Microsoft.Build.Framework +open Ionide.KeepAChangelog.Tasks +open Shouldly + +// [] +// let ``works for 'ci' type with default config`` () = +// let buildEngine = Mock() +// let errors = ResizeArray() + +// buildEngine +// .Setup(fun x -> x.LogErrorEvent(It.IsAny())) +// .Callback(fun args -> errors.Add(args)) +// |> ignore + +// let item = Mock() +// item.Setup(fun x -> x.GetMetadata("MaximeTest")).Returns("test") |> ignore + +// let myTask = ParseChangelog(ChangelogFile = "MyChangelog.md") +// myTask.BuildEngine <- buildEngine.Object + +// let success = myTask.Execute() + +// success.ShouldBeTrue() diff --git a/tests/Ionide.KeepAChangelog.Tasks.Test.fsproj b/tests/Ionide.KeepAChangelog.Tasks.Test.fsproj new file mode 100644 index 0000000..2e03bf3 --- /dev/null +++ b/tests/Ionide.KeepAChangelog.Tasks.Test.fsproj @@ -0,0 +1,31 @@ + + + + Exe + net8.0 + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/tests/Setup.fs b/tests/Setup.fs new file mode 100644 index 0000000..bb6f1b1 --- /dev/null +++ b/tests/Setup.fs @@ -0,0 +1,27 @@ +module Tests.Setup + +open Fixie +open System +open System.Collections.Generic +open System.Reflection + +[] +type TestAttribute() = + inherit Attribute() + +type TestModuleDiscovery() = + interface IDiscovery with + member _.TestClasses(concreteClasses: IEnumerable) = + concreteClasses + |> Seq.filter (fun cls -> + cls.GetMembers() |> Seq.exists (fun m -> m.Has()) + ) + + member _.TestMethods(publicMethods: IEnumerable) = + publicMethods |> Seq.filter (fun x -> x.Has() && x.IsStatic) + +type TestProject() = + interface ITestProject with + member _.Configure(configuration: TestConfiguration, environment: TestEnvironment) = + configuration.Conventions.Add() + \ No newline at end of file diff --git a/tests/UnitTests.fs b/tests/UnitTests.fs new file mode 100644 index 0000000..0bf664f --- /dev/null +++ b/tests/UnitTests.fs @@ -0,0 +1,105 @@ +module Tests.UnitTests + +open Tests.Setup +open Moq +open Microsoft.Build.Framework +open Ionide.KeepAChangelog.Tasks +open Shouldly +open Workspace + +type TestContext = + { + BuildEngine: Mock + Errors: ResizeArray + } + + member this.PrintErrors() = + this.Errors |> Seq.iter (fun error -> printfn "Error: %s" error.Message) + +let private setupBuildEngine () = + let context = + { + BuildEngine = Mock() + Errors = ResizeArray() + } + + context.BuildEngine + .Setup(fun engine -> engine.LogErrorEvent(It.IsAny())) + .Callback(fun (args: BuildErrorEventArgs) -> context.Errors.Add(args)) + |> ignore + + context + +[] +let ``task fails when changelog file does not exist`` () = + let context = setupBuildEngine () + + let myTask = ParseChangeLogs(ChangelogFile = "ThisFileDoesNotExist.md") + myTask.BuildEngine <- context.BuildEngine.Object + + let success = myTask.Execute() + + success.ShouldBeFalse() + context.Errors.Count.ShouldBe(1) + context.Errors.[0].Code.ShouldBe("IKC0001") + +[] +let ``task succeeds when changelog file exists (relative path)`` () = + let context = setupBuildEngine () + + // When running tests, the working directory is where the dll is located + let myTask = ParseChangeLogs(ChangelogFile = "../../../fixtures/CHANGELOG.md") + + myTask.BuildEngine <- context.BuildEngine.Object + + let success = myTask.Execute() + + context.PrintErrors() + + success.ShouldBeTrue() + context.Errors.Count.ShouldBe(0) + +[] +let ``task succeeds when changelog file exists (absolute path)`` () = + let context = setupBuildEngine () + + let myTask = ParseChangeLogs(ChangelogFile = Workspace.fixtures.``CHANGELOG.md``) + myTask.BuildEngine <- context.BuildEngine.Object + + let success = myTask.Execute() + + success.ShouldBeTrue() + context.Errors.Count.ShouldBe(0) + +[] +let ``task fails when changelog file is invalid`` () = + let context = setupBuildEngine () + + let myTask = + ParseChangeLogs(ChangelogFile = Workspace.fixtures.``CHANGELOG_invalid.md``) + + myTask.BuildEngine <- context.BuildEngine.Object + + let success = myTask.Execute() + + success.ShouldBeFalse() + context.Errors.Count.ShouldBe(1) + context.Errors.[0].Code.ShouldBe("IKC0002") + + +[] +let ``task correctly parses detailes from changelog file`` () = + let context = setupBuildEngine () + let myTask = + ParseChangeLogs(ChangelogFile = Workspace.fixtures.``CHANGELOG_detailed.md``) + + myTask.BuildEngine <- context.BuildEngine.Object + + let success = myTask.Execute() + success.ShouldBeTrue "Should have successfully parsed the changelog data" + myTask.AllReleasedChangelogs.Length.ShouldBe(9, "Should have 9 versions") + myTask.CurrentReleaseChangelog.ItemSpec.ShouldBe("0.1.8", "Should have the most recent version") + myTask.CurrentReleaseChangelog.GetMetadata("Date").ShouldBe("2022-03-31", "Should have the most recent version's date") + myTask.CurrentReleaseChangelog.MetadataNames |> Seq.cast |> _.ShouldContain("Changed", "Should have changed metadata") + myTask.CurrentReleaseChangelog.MetadataNames |> Seq.cast |> _.ShouldContain("Date", "Should have date metadata") + diff --git a/tests/Workspace.fs b/tests/Workspace.fs new file mode 100644 index 0000000..111d882 --- /dev/null +++ b/tests/Workspace.fs @@ -0,0 +1,5 @@ +module Workspace + +open EasyBuild.FileSystemProvider + +type Workspace = RelativeFileSystem<"."> diff --git a/tests/fixtures/CHANGELOG.md b/tests/fixtures/CHANGELOG.md new file mode 100644 index 0000000..417eb98 --- /dev/null +++ b/tests/fixtures/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## [Unreleased] + +## [0.1.0] - 2022-01-13 + +### Added + +- Created the package diff --git a/tests/fixtures/CHANGELOG_detailed.md b/tests/fixtures/CHANGELOG_detailed.md new file mode 100644 index 0000000..8bc7ed5 --- /dev/null +++ b/tests/fixtures/CHANGELOG_detailed.md @@ -0,0 +1,57 @@ +# Changelog + +## [0.1.8] - 2022-03-31 + +### Changed + +- Minor packaging fix for non-Core MSBuild versions + +## [0.1.7] - 2022-03-31 + +### Changed + +- Better packaging of the task to prevent task DLL dependencies from impacting your project's dependencies +- Updated the parser to provide to `ToMarkdown()` member for more general use + +## [0.1.6] - 2022-03-31 + +### Changed + +- bump SDK + +## [0.1.5] - 2022-03-31 + +### Fixed + +- Embed a deps.json file in the package. + +## [0.1.4] - 2022-03-20 + +### Fixed + +- Support supplying package versions for project-to-project references in multitargeting scenarios. + + +## [0.1.3] - 2022-03-20 + +### Fixed + +- Support supplying package versions for project-to-project references. + +## [0.1.2] - 2022-02-13 + +### Added + +- Now supports multiTargeted builds and packs by the addition of the buildMultiTargeting folder. The outer build is hooked at the GenerateNuspec target. + +## [0.1.1] - 2022-01-14 + +### Added + +- Now writes an assembly-level AssemblyMetadataAttribute with the key "BuildDate" whose value is the `YYYY-mm-dd`-formatted date in the release changelog + +## [0.1.0] - 2022-01-13 + +### Added + +- Created the package \ No newline at end of file diff --git a/tests/fixtures/CHANGELOG_invalid.md b/tests/fixtures/CHANGELOG_invalid.md new file mode 100644 index 0000000..f2aa90d --- /dev/null +++ b/tests/fixtures/CHANGELOG_invalid.md @@ -0,0 +1,9 @@ +# Changelog + +## [Unreleased] + +## [this is not a valid version] - 2022-01-13 + +### Added + +- Created the package diff --git a/tests/fixtures/Directory.Build.props b/tests/fixtures/Directory.Build.props new file mode 100644 index 0000000..51207d2 --- /dev/null +++ b/tests/fixtures/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/tests/fixtures/valid/Simple.fsproj b/tests/fixtures/valid/Simple.fsproj new file mode 100644 index 0000000..7d620c7 --- /dev/null +++ b/tests/fixtures/valid/Simple.fsproj @@ -0,0 +1,9 @@ + + + + net6.0 + + + + + diff --git a/tests/fixtures/valid/packages.lock.json b/tests/fixtures/valid/packages.lock.json new file mode 100644 index 0000000..e3c801d --- /dev/null +++ b/tests/fixtures/valid/packages.lock.json @@ -0,0 +1,13 @@ +{ + "version": 2, + "dependencies": { + "net6.0": { + "Ionide.KeepAChangelog.Tasks": { + "type": "Direct", + "requested": "[1.0.0-test-002, )", + "resolved": "1.0.0-test-002", + "contentHash": "FOf+ufy7DdFURwhkS/d2c51C0CGtBk5zikGnyfoSf+tWvukJeD3xl2I5cXKeX8F1l/bzz8GA1y0FoGT+6pNMAg==" + } + } + } +} \ No newline at end of file diff --git a/tests/packages.lock.json b/tests/packages.lock.json new file mode 100644 index 0000000..6e34b1a --- /dev/null +++ b/tests/packages.lock.json @@ -0,0 +1,264 @@ +{ + "version": 2, + "dependencies": { + "net8.0": { + "DotNet.ReproducibleBuilds": { + "type": "Direct", + "requested": "[1.2.25, )", + "resolved": "1.2.25", + "contentHash": "xCXiw7BCxHJ8pF6wPepRUddlh2dlQlbr81gXA72hdk4FLHkKXas7EH/n+fk5UCA/YfMqG1Z6XaPiUjDbUNBUzg==" + }, + "EasyBuild.FileSystemProvider": { + "type": "Direct", + "requested": "[0.3.0, )", + "resolved": "0.3.0", + "contentHash": "gdVJpqcMDJm4IfmITy3MtpEn/lo9pH8PirVlENtXGX9Sdw3rCgoo9ch1TAthUseh28RcUGWwza9BmEWlrQX/Aw==" + }, + "Fixie.TestAdapter": { + "type": "Direct", + "requested": "[3.4.0, )", + "resolved": "3.4.0", + "contentHash": "KQBF/t82Ax/09CuF4GuYOCshfA72YGvMh3t8EGLeqsCLdZmWhR6Lz0IIsbDkS9DrECK7l03WTufHe0D7Mq6WBw==", + "dependencies": { + "Fixie": "[3.4.0]", + "Microsoft.NET.Test.Sdk": "17.8.0", + "Mono.Cecil": "0.11.5" + } + }, + "FSharp.Core": { + "type": "Direct", + "requested": "[7.0.300, )", + "resolved": "7.0.300", + "contentHash": "8vvItREJ1l5lcp3vBCSJ1mFevVAhR48I34DuF/EoUa7o1KlFpQpagyuZkVYMAsHPIjdp47ZxM9sI4eqeXaeWkA==" + }, + "KeepAChangeLogParser": { + "type": "Direct", + "requested": "[1.2.4, )", + "resolved": "1.2.4", + "contentHash": "895vwfwdD52hTtmwZDQgXLlpbMia+vz+ZpkLCs7idBR+XD3mECwRULiBch4+1+7LCCW0FZMxKoG4GOD4VkqO9Q==", + "dependencies": { + "CSharpFunctionalExtensions": "2.42.5", + "Roslynator.Analyzers": "4.12.4" + } + }, + "Microsoft.Build.Utilities.Core": { + "type": "Direct", + "requested": "[17.8.3, )", + "resolved": "17.8.3", + "contentHash": "ex8Sjx02q0FmForLRFItB82sJx5s2JRWIpJgYDW1g7xLUoFlKLoNp67UwS5xN8YcYBkT7vRxXeYx/dQ96KaWtg==", + "dependencies": { + "Microsoft.Build.Framework": "17.8.3", + "Microsoft.NET.StringTools": "17.8.3", + "Microsoft.VisualStudio.Setup.Configuration.Interop": "3.2.2146", + "System.Collections.Immutable": "7.0.0", + "System.Configuration.ConfigurationManager": "7.0.0" + } + }, + "Moq": { + "type": "Direct", + "requested": "[4.20.72, )", + "resolved": "4.20.72", + "contentHash": "EA55cjyNn8eTNWrgrdZJH5QLFp2L43oxl1tlkoYUKIE9pRwL784OWiTXeCV5ApS+AMYEAlt7Fo03A2XfouvHmQ==", + "dependencies": { + "Castle.Core": "5.1.1" + } + }, + "Semver": { + "type": "Direct", + "requested": "[2.3.0, )", + "resolved": "2.3.0", + "contentHash": "4vYo1zqn6pJ1YrhjuhuOSbIIm0CpM47grbpTJ5ABjOlfGt/EhMEM9ed4MRK5Jr6gVnntWDqOUzGeUJp68PZGjw==" + }, + "Shouldly": { + "type": "Direct", + "requested": "[4.2.1, )", + "resolved": "4.2.1", + "contentHash": "dKAKiSuhLKqD2TXwLKtqNg1nwzJcIKOOMncZjk9LYe4W+h+SCftpWdxwR79YZUIHMH+3Vu9s0s0UHNrgICLwRQ==", + "dependencies": { + "DiffEngine": "11.3.0", + "EmptyFiles": "4.4.0" + } + }, + "SimpleExec": { + "type": "Direct", + "requested": "[12.0.0, )", + "resolved": "12.0.0", + "contentHash": "ptxlWtxC8vM6Y6e3h9ZTxBBkOWnWrm/Sa1HT+2i1xcXY3Hx2hmKDZP5RShPf8Xr9D+ivlrXNy57ktzyH8kyt+Q==" + }, + "Castle.Core": { + "type": "Transitive", + "resolved": "5.1.1", + "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==", + "dependencies": { + "System.Diagnostics.EventLog": "6.0.0" + } + }, + "CSharpFunctionalExtensions": { + "type": "Transitive", + "resolved": "2.42.5", + "contentHash": "Do9Q7Nyfx2G4HRt96aB6yoP5Z9ic3jqBHLrf35tULzsWlFoBlkQZT62aXCiv7UmVmF56OUR+a3JoSSs140hfHQ==" + }, + "DiffEngine": { + "type": "Transitive", + "resolved": "11.3.0", + "contentHash": "k0ZgZqd09jLZQjR8FyQbSQE86Q7QZnjEzq1LPHtj1R2AoWO8sjV5x+jlSisL7NZAbUOI4y+7Bog8gkr9WIRBGw==", + "dependencies": { + "EmptyFiles": "4.4.0", + "System.Management": "6.0.1" + } + }, + "EmptyFiles": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "gwJEfIGS7FhykvtZoscwXj/XwW+mJY6UbAZk+qtLKFUGWC95kfKXnj8VkxsZQnWBxJemM/q664rGLN5nf+OHZw==" + }, + "Fixie": { + "type": "Transitive", + "resolved": "3.4.0", + "contentHash": "Rn+KhQIobYpMC8+S3Fk48wgpo8zljYsgT+M84q5csNJM5AzyVebcIIPAv5DXb+lJuKaOCG3ntpTdTEUPOJAHOA==" + }, + "Microsoft.Build.Framework": { + "type": "Transitive", + "resolved": "17.8.3", + "contentHash": "NrQZJW8TlKVPx72yltGb8SVz3P5mNRk9fNiD/ao8jRSk48WqIIdCn99q4IjlVmPcruuQ+yLdjNQLL8Rb4c916g==" + }, + "Microsoft.CodeCoverage": { + "type": "Transitive", + "resolved": "17.8.0", + "contentHash": "KC8SXWbGIdoFVdlxKk9WHccm0llm9HypcHMLUUFabRiTS3SO2fQXNZfdiF3qkEdTJhbRrxhdRxjL4jbtwPq4Ew==" + }, + "Microsoft.NET.StringTools": { + "type": "Transitive", + "resolved": "17.8.3", + "contentHash": "y6DiuacjlIfXH3XVQG5htf+4oheinZAo7sHbITB3z7yCXQec48f9ZhGSXkr+xn1bfl73Yc3ZQEW2peJ5X68AvQ==" + }, + "Microsoft.NET.Test.Sdk": { + "type": "Transitive", + "resolved": "17.8.0", + "contentHash": "BmTYGbD/YuDHmApIENdoyN1jCk0Rj1fJB0+B/fVekyTdVidr91IlzhqzytiUgaEAzL1ZJcYCme0MeBMYvJVzvw==", + "dependencies": { + "Microsoft.CodeCoverage": "17.8.0", + "Microsoft.TestPlatform.TestHost": "17.8.0" + } + }, + "Microsoft.TestPlatform.ObjectModel": { + "type": "Transitive", + "resolved": "17.8.0", + "contentHash": "AYy6vlpGMfz5kOFq99L93RGbqftW/8eQTqjT9iGXW6s9MRP3UdtY8idJ8rJcjeSja8A18IhIro5YnH3uv1nz4g==", + "dependencies": { + "NuGet.Frameworks": "6.5.0", + "System.Reflection.Metadata": "1.6.0" + } + }, + "Microsoft.TestPlatform.TestHost": { + "type": "Transitive", + "resolved": "17.8.0", + "contentHash": "9ivcl/7SGRmOT0YYrHQGohWiT5YCpkmy/UEzldfVisLm6QxbLaK3FAJqZXI34rnRLmqqDCeMQxKINwmKwAPiDw==", + "dependencies": { + "Microsoft.TestPlatform.ObjectModel": "17.8.0", + "Newtonsoft.Json": "13.0.1" + } + }, + "Microsoft.VisualStudio.Setup.Configuration.Interop": { + "type": "Transitive", + "resolved": "3.2.2146", + "contentHash": "gMq8uGy8zTIp0kQGTI45buZC3JOStGJyjGD8gksskk83aQISW65IESErLE/WDT7Bdy+QWbdUi7QyO1LEzUSOFA==" + }, + "Microsoft.Win32.SystemEvents": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "2nXPrhdAyAzir0gLl8Yy8S5Mnm/uBSQQA7jEsILOS1MTyS7DbmV1NgViMtvV1sfCD1ebITpNwb1NIinKeJgUVQ==" + }, + "Mono.Cecil": { + "type": "Transitive", + "resolved": "0.11.5", + "contentHash": "fxfX+0JGTZ8YQeu1MYjbBiK2CYTSzDyEeIixt+yqKKTn7FW8rv7JMY70qevup4ZJfD7Kk/VG/jDzQQTpfch87g==" + }, + "Newtonsoft.Json": { + "type": "Transitive", + "resolved": "13.0.1", + "contentHash": "ppPFpBcvxdsfUonNcvITKqLl3bqxWbDCZIzDWHzjpdAHRFfZe0Dw9HmA0+za13IdyrgJwpkDTDA9fHaxOrt20A==" + }, + "NuGet.Frameworks": { + "type": "Transitive", + "resolved": "6.5.0", + "contentHash": "QWINE2x3MbTODsWT1Gh71GaGb5icBz4chS8VYvTgsBnsi8esgN6wtHhydd7fvToWECYGq7T4cgBBDiKD/363fg==" + }, + "Roslynator.Analyzers": { + "type": "Transitive", + "resolved": "4.12.4", + "contentHash": "isl8hAh7yFNjyBEC4YlTSi+xGBblqBUC/2MCMmnBPwuXPewb7XYnMRzT3vXbP/gOGwT8hZUOy1g/aRH3lAF/NQ==" + }, + "System.CodeDom": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "CPc6tWO1LAer3IzfZufDBRL+UZQcj5uS207NHALQzP84Vp/z6wF0Aa0YZImOQY8iStY0A2zI/e3ihKNPfUm8XA==" + }, + "System.Collections.Immutable": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "dQPcs0U1IKnBdRDBkrCTi1FoajSTBzLcVTpjO4MBCMC7f4pDOIPzgBoX8JjG7X6uZRJ8EBxsi8+DR1JuwjnzOQ==" + }, + "System.Configuration.ConfigurationManager": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "WvRUdlL1lB0dTRZSs5XcQOd5q9MYNk90GkbmRmiCvRHThWiojkpGqWdmEDJdXyHbxG/BhE5hmVbMfRLXW9FJVA==", + "dependencies": { + "System.Diagnostics.EventLog": "7.0.0", + "System.Security.Cryptography.ProtectedData": "7.0.0", + "System.Security.Permissions": "7.0.0" + } + }, + "System.Diagnostics.EventLog": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "eUDP47obqQm3SFJfP6z+Fx2nJ4KKTQbXB4Q9Uesnzw9SbYdhjyoGXuvDn/gEmFY6N5Z3bFFbpAQGA7m6hrYJCw==" + }, + "System.Drawing.Common": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "KIX+oBU38pxkKPxvLcLfIkOV5Ien8ReN78wro7OF5/erwcmortzeFx+iBswlh2Vz6gVne0khocQudGwaO1Ey6A==", + "dependencies": { + "Microsoft.Win32.SystemEvents": "7.0.0" + } + }, + "System.Management": { + "type": "Transitive", + "resolved": "6.0.1", + "contentHash": "10J1D0h/lioojphfJ4Fuh5ZUThT/xOVHdV9roGBittKKNP2PMjrvibEdbVTGZcPra1399Ja3tqIJLyQrc5Wmhg==", + "dependencies": { + "System.CodeDom": "6.0.0" + } + }, + "System.Reflection.Metadata": { + "type": "Transitive", + "resolved": "1.6.0", + "contentHash": "COC1aiAJjCoA5GBF+QKL2uLqEBew4JsCkQmoHKbN3TlOZKa2fKLz5CpiRQKDz0RsAOEGsVKqOD5bomsXq/4STQ==" + }, + "System.Security.Cryptography.ProtectedData": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "xSPiLNlHT6wAHtugASbKAJwV5GVqQK351crnILAucUioFqqieDN79evO1rku1ckt/GfjIn+b17UaSskoY03JuA==" + }, + "System.Security.Permissions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "Vmp0iRmCEno9BWiskOW5pxJ3d9n+jUqKxvX4GhLwFhnQaySZmBN2FuC0N5gjFHgyFMUjC5sfIJ8KZfoJwkcMmA==", + "dependencies": { + "System.Windows.Extensions": "7.0.0" + } + }, + "System.Windows.Extensions": { + "type": "Transitive", + "resolved": "7.0.0", + "contentHash": "bR4qdCmssMMbo9Fatci49An5B1UaVJZHKNq70PRgzoLYIlitb8Tj7ns/Xt5Pz1CkERiTjcVBDU2y1AVrPBYkaw==", + "dependencies": { + "System.Drawing.Common": "7.0.0" + } + }, + "ionide.keepachangelog.tasks": { + "type": "Project" + } + } + } +} \ No newline at end of file