Skip to content

Commit 3ee60b4

Browse files
authored
Parse references at the end of a changelog as a separate part of a changelog. (#2779)
* Parse references at the end of a changelog as a separate part of a changelog. * Test cases for changelog parsing with references in extra section
1 parent 4d5de20 commit 3ee60b4

File tree

2 files changed

+227
-11
lines changed

2 files changed

+227
-11
lines changed

src/app/Fake.Core.ReleaseNotes/Changelog.fs

Lines changed: 91 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,16 @@ module Changelog =
108108
| Security s -> sprintf "Security: %s" s.CleanedText
109109
| Custom (h, s) -> sprintf "%s: %s" h s.CleanedText
110110

111+
member x.ChangeText() =
112+
match x with
113+
| Added (changeText)
114+
| Changed (changeText)
115+
| Deprecated (changeText)
116+
| Removed (changeText)
117+
| Fixed (changeText)
118+
| Security (changeText)
119+
| Custom (_, changeText) -> changeText
120+
111121
/// Create a new change type changelog entry
112122
static member New(header: string, line: string) : Change =
113123
let text =
@@ -267,6 +277,32 @@ module Changelog =
267277

268278
assemblyVersion, nugetVersion
269279

280+
/// <summary>
281+
/// A version or "Unreleased"
282+
/// </summary>
283+
type ReferenceVersion =
284+
| SemVerRef of SemVerInfo
285+
| UnreleasedRef
286+
287+
override x.ToString() =
288+
match x with
289+
| SemVerRef (semVerInfo) -> semVerInfo.ToString()
290+
| UnreleasedRef -> "Unreleased"
291+
292+
/// <summary>
293+
/// A reference from a version to a repository URL (e.g. a tag or a compare link)
294+
/// </summary>
295+
/// <code>
296+
/// [Unreleased]: https://github.com/user/MyCoolNewLib.git/compare/v0.1.0...HEAD
297+
/// [0.1.0]: https://github.com/user/MyCoolNewLib.git/releases/tag/v0.1.0
298+
/// </code>
299+
type Reference =
300+
{ SemVer: ReferenceVersion
301+
RepoUrl: Uri }
302+
303+
override x.ToString() =
304+
sprintf "[%s]: %s" (x.SemVer.ToString()) (x.RepoUrl.ToString())
305+
270306
/// <summary>
271307
/// Holds data for a changelog file, which include changelog entries an other metadata
272308
/// </summary>
@@ -283,17 +319,21 @@ module Changelog =
283319

284320
/// The change log entries
285321
Entries: ChangelogEntry list
322+
323+
/// The references to repository URLs
324+
References: Reference list
286325
}
287326

288327
/// the latest change log entry
289328
member x.LatestEntry = x.Entries |> Seq.head
290329

291330
/// Create a new changelog record from given data
292-
static member New(header, description, unreleased, entries) =
331+
static member New(header, description, unreleased, entries, references) =
293332
{ Header = header
294333
Description = description
295334
Unreleased = unreleased
296-
Entries = entries }
335+
Entries = entries
336+
References = references }
297337

298338
/// Promote an unreleased changelog entry to a released one
299339
member x.PromoteUnreleased(assemblyVersion: string, nugetVersion: string) : Changelog =
@@ -311,7 +351,7 @@ module Changelog =
311351
)
312352

313353
let unreleased' = Some { Description = None; Changes = [] }
314-
Changelog.New(x.Header, x.Description, unreleased', newEntry :: x.Entries)
354+
Changelog.New(x.Header, x.Description, unreleased', newEntry :: x.Entries, x.References)
315355

316356
/// Promote an unreleased changelog entry to a released one using version number
317357
member x.PromoteUnreleased(version: string) : Changelog =
@@ -346,7 +386,10 @@ module Changelog =
346386
| "" -> "Changelog"
347387
| h -> h
348388

349-
(sprintf "# %s\n\n%s\n\n%s" header description entries)
389+
let references =
390+
x.References |> List.map (fun reference -> reference.ToString()) |> joinLines
391+
392+
$"# {header}\n\n{description}\n\n{entries}\n\n{references}"
350393
|> fixMultipleNewlines
351394
|> String.trim
352395

@@ -358,8 +401,9 @@ module Changelog =
358401
/// <param name="description">the descriptive text for changelog</param>
359402
/// <param name="unreleased">the unreleased list of changelog entries</param>
360403
/// <param name="entries">the list of changelog entries</param>
361-
let createWithCustomHeader header description unreleased entries =
362-
Changelog.New(header, description, unreleased, entries)
404+
/// <param name="references">the list of references</param>
405+
let createWithCustomHeader header description unreleased entries references =
406+
Changelog.New(header, description, unreleased, entries, references)
363407

364408
/// <summary>
365409
/// Create a changelog with given data
@@ -368,16 +412,17 @@ module Changelog =
368412
/// <param name="description">the descriptive text for changelog </param>
369413
/// <param name="unreleased">the unreleased list of changelog entries</param>
370414
/// <param name="entries">the list of changelog entries</param>
371-
let create description unreleased entries =
372-
createWithCustomHeader "Changelog" description unreleased entries
415+
/// <param name="references">the list of references</param>
416+
let create description unreleased entries references =
417+
createWithCustomHeader "Changelog" description unreleased entries references
373418

374419
/// <summary>
375420
/// Create a changelog with given entries and default values for other data including
376421
/// header and description.
377422
/// </summary>
378423
///
379424
/// <param name="entries">the list of changelog entries</param>
380-
let fromEntries entries = create None None entries
425+
let fromEntries entries = create None None entries []
381426

382427
let internal isMainHeader line : bool = "# " <* line
383428
let internal isVersionHeader line : bool = "## " <* line
@@ -538,7 +583,43 @@ module Changelog =
538583
| h :: _ -> h
539584
| _ -> "Changelog"
540585

541-
Changelog.New(header, description, unreleased, entries)
586+
// Move references from last changelog entry into references.
587+
let entriesWithoutReferences, references =
588+
let referenceRegex =
589+
$"""^\[(({nugetRegex.ToString()})|Unreleased)\]: +(http[s]://.+)$"""
590+
|> String.getRegEx
591+
592+
match entries with
593+
| [] -> [], []
594+
| entries ->
595+
let front, lastChange = entries |> List.splitAt (List.length entries - 1)
596+
let last = List.head lastChange
597+
598+
let refChanges, trueChanges =
599+
last.Changes
600+
|> List.partition (fun (change: Change) ->
601+
referenceRegex.Match(change.ChangeText().CleanedText).Success)
602+
603+
let references =
604+
refChanges
605+
|> List.map (fun change ->
606+
let referenceMatch = referenceRegex.Match(change.ChangeText().CleanedText)
607+
let version = referenceMatch.Groups[1].Value
608+
let uri = referenceMatch.Groups[6].Value
609+
610+
{ SemVer =
611+
if version = "Unreleased" then
612+
UnreleasedRef
613+
else
614+
SemVerRef(SemVer.parse version)
615+
RepoUrl = Uri(uri) })
616+
617+
let newLastEntry =
618+
ChangelogEntry.New(last.AssemblyVersion, last.NuGetVersion, trueChanges)
619+
620+
front @ [ newLastEntry ], references
621+
622+
Changelog.New(header, description, unreleased, entriesWithoutReferences, references)
542623

543624
/// <summary>
544625
/// Parses a Changelog text file and returns the latest changelog.

src/test/Fake.Core.UnitTests/Fake.Core.ReleaseNotes.fs

Lines changed: 136 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,37 @@ module Fake.Core.ReleaseNotesTests
22

33
open Fake.Core
44
open Expecto
5+
open System
6+
7+
[<Literal>]
8+
let private changelogReleasesText =
9+
"""# Changelog
10+
11+
All notable changes to this project will be documented in this file.
12+
13+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
14+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
15+
16+
## Unreleased
17+
18+
### Changed
19+
- Foo 2
20+
21+
## [0.1.0-pre.2] - 2023-10-19
22+
23+
### Added
24+
- Foo 1
25+
26+
## [0.1.0-pre.1] - 2023-10-11
27+
28+
### Added
29+
- Foo 0"""
30+
31+
[<Literal>]
32+
let private changelogReferencesText =
33+
"""[Unreleased]: https://github.com/bogus/Foo/compare/v0.1.0-pre.2...HEAD
34+
[0.1.0-pre.2]: https://github.com/bogus/Foo/releases/tag/v0.1.0-pre.2
35+
[0.1.0-pre.1]: https://github.com/bogus/Foo/releases/tag/v0.1.0-pre.1"""
536

637
[<Tests>]
738
let tests =
@@ -245,4 +276,108 @@ let tests =
245276
checkPreRelease releaseNotesLines_case2 (Some "---RC-SNAPSHOT.12.9.1--.12") (Some "788")
246277
checkPreRelease releaseNotesLines_case3 (Some "---R-S.12.9.1--.12") (Some "meta")
247278
checkPreRelease releaseNotesLines_case4 (None) (Some "0.build.1-rc.10000aaa-kk-0.1")
248-
checkPreRelease releaseNotesLines_case5 (Some "0A.is.legal") (None) ] ]
279+
checkPreRelease releaseNotesLines_case5 (Some "0A.is.legal") (None) ]
280+
281+
// https://keepachangelog.com
282+
testList
283+
"Changelog"
284+
[ testCase "Test that we can parse changelog without references"
285+
<| fun _ ->
286+
let changelog = changelogReleasesText |> String.splitStr "\n" |> Changelog.parse
287+
288+
Expect.isEmpty changelog.References "References not empty"
289+
Expect.isSome changelog.Unreleased "Unreleased section empty"
290+
Expect.hasLength changelog.Entries 2 "Wrong number of release entries parsed"
291+
testCase "Test that we can parse changelog with references"
292+
<| fun _ ->
293+
let changelogText = changelogReleasesText + "\n\n" + changelogReferencesText
294+
let changelog = changelogText |> String.splitStr "\n" |> Changelog.parse
295+
296+
Expect.hasLength changelog.References 3 "Wrong number of references parsed"
297+
298+
Expect.hasLength
299+
(changelog.References
300+
|> List.filter (fun r ->
301+
match r.SemVer with
302+
| Changelog.SemVerRef (_) -> true
303+
| _ -> false))
304+
2
305+
"Wrong number of released references parsed"
306+
307+
Expect.hasLength
308+
(changelog.References
309+
|> List.filter (fun r ->
310+
match r.SemVer with
311+
| Changelog.SemVerRef (_) -> false
312+
| _ -> true))
313+
1
314+
"Wrong number of unreleased references parsed"
315+
316+
Expect.hasLength changelog.References 3 "Wrong number of references parsed"
317+
Expect.isSome changelog.Unreleased "Unreleased section empty"
318+
Expect.hasLength changelog.Entries 2 "Wrong number of release entries parsed"
319+
testCase "Test that references are not in the last changelog entry"
320+
<| fun _ ->
321+
let changelogText = changelogReleasesText + "\n\n" + changelogReferencesText
322+
let changelog = changelogText |> String.splitStr "\n" |> Changelog.parse
323+
let lastEntry = changelog.Entries |> List.last
324+
let lastChanges = lastEntry.Changes
325+
326+
Expect.isFalse
327+
(lastChanges
328+
|> List.exists (fun change ->
329+
change.ChangeText().CleanedText.Contains("https://github.com/bogus/Foo/")))
330+
"URL of reference contained in change text"
331+
testCase "Test that a release and reference can be added and correctly turned into a string"
332+
<| fun _ ->
333+
let changelogText = changelogReleasesText + "\n\n" + changelogReferencesText
334+
let changelog = changelogText |> String.splitStr "\n" |> Changelog.parse
335+
let versionText = "0.1.0-pre.3"
336+
let semVerInfo = SemVer.parse versionText
337+
338+
let newUnreleasedRef =
339+
{ Changelog.Reference.SemVer = Changelog.UnreleasedRef
340+
Changelog.Reference.RepoUrl = Uri("https://github.com/bogus/Foo/compare/v0.1.0-pre.3...HEAD") }
341+
342+
let releasedRefs =
343+
changelog.References
344+
|> List.filter (fun r ->
345+
match r.SemVer with
346+
| Changelog.SemVerRef (_) -> true
347+
| _ -> false)
348+
349+
let newReference =
350+
{ Changelog.Reference.SemVer = Changelog.SemVerRef(semVerInfo)
351+
Changelog.Reference.RepoUrl = Uri("https://github.com/bogus/Foo/releases/tag/v0.1.0-pre.3") }
352+
353+
let newFixed =
354+
Changelog.Fixed(
355+
{ CleanedText = "Foo 3"
356+
OriginalText = None }
357+
)
358+
359+
let newReleaseEntry =
360+
Changelog.ChangelogEntry.New(
361+
"",
362+
versionText,
363+
Some(DateTime(2023, 11, 23)),
364+
None,
365+
[ newFixed ],
366+
false
367+
)
368+
369+
let changelogNew =
370+
{ changelog with
371+
Entries = newReleaseEntry :: changelog.Entries
372+
References = [ newUnreleasedRef; newReference ] @ releasedRefs }
373+
374+
let expectedEnd =
375+
"""[Unreleased]: https://github.com/bogus/Foo/compare/v0.1.0-pre.3...HEAD
376+
[0.1.0-pre.3]: https://github.com/bogus/Foo/releases/tag/v0.1.0-pre.3
377+
[0.1.0-pre.2]: https://github.com/bogus/Foo/releases/tag/v0.1.0-pre.2
378+
[0.1.0-pre.1]: https://github.com/bogus/Foo/releases/tag/v0.1.0-pre.1"""
379+
380+
Expect.stringEnds
381+
(changelogNew.ToString())
382+
expectedEnd
383+
"Invalid references at end of changelog text" ] ]

0 commit comments

Comments
 (0)