-
Notifications
You must be signed in to change notification settings - Fork 30
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #305 from Lombiq/issue/OFFI-193
OFFI-193: VersionTree
- Loading branch information
Showing
6 changed files
with
350 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
#nullable enable | ||
|
||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
|
||
namespace Lombiq.HelpfulLibraries.Common.Utilities; | ||
|
||
/// <summary> | ||
/// Groups <see cref="Version"/> instances into a tree structure based on the specified version parts, so they can be | ||
/// selected by number using indexing. | ||
/// </summary> | ||
/// <param name="Versions">All versions in the current subtree.</param> | ||
/// <param name="SubVersions">Versions grouped by the directly next version part.</param> | ||
public record VersionTree(IReadOnlyList<Version> Versions, IReadOnlyDictionary<int, VersionTree> SubVersions) | ||
{ | ||
/// <summary> | ||
/// Gets the subtree for the provided key, if it exists (otherwise <see langword="null"/>). If the <paramref | ||
/// name="index"/> is negative, this instance is returned instead. | ||
/// </summary> | ||
public VersionTree? this[int index] | ||
{ | ||
get | ||
{ | ||
if (index < 0) return this; | ||
return SubVersions.TryGetValue(index, out var sub) ? sub : null; | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Gets all versions that match the provided <paramref name="version"/>, that can be partial/floating. | ||
/// </summary> | ||
public VersionTree? this[Version? version] => | ||
version is null | ||
? this | ||
: this[version.Major]?[version.Minor]?[version.Build]?[version.Revision]; | ||
|
||
/// <summary> | ||
/// Creates a new tree from a copy of the provided <paramref name="versions"/>. | ||
/// </summary> | ||
public static VersionTree Create(IEnumerable<Version> versions) | ||
{ | ||
var allVersions = versions.ToList(); | ||
|
||
var majorVersions = | ||
FromVersions(allVersions, version => version.Major, major => | ||
FromVersions(major, version => version.Minor, minor => | ||
FromVersions(minor, version => version.Build, revision => | ||
FromVersions(revision, version => version.Revision, _ => [])))); | ||
|
||
return new(allVersions, majorVersions); | ||
} | ||
|
||
private static Dictionary<int, VersionTree> FromVersions( | ||
IEnumerable<Version> versions, | ||
Func<Version, int> selector, | ||
Func<IEnumerable<Version>, Dictionary<int, VersionTree>> subSelector) => | ||
versions | ||
.Where(version => selector(version) >= 0) | ||
.GroupBy(selector) | ||
.ToDictionary( | ||
group => group.Key, | ||
items => new VersionTree([.. items], subSelector(items))); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
61 changes: 61 additions & 0 deletions
61
Lombiq.HelpfulLibraries.Tests/UnitTests/Utilities/VersionTreeTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
using Lombiq.HelpfulLibraries.Common.Utilities; | ||
using Shouldly; | ||
using System; | ||
using System.Collections.Generic; | ||
using System.IO; | ||
using System.Linq; | ||
using System.Text.Json; | ||
using Xunit; | ||
|
||
namespace Lombiq.HelpfulLibraries.Tests.UnitTests.Utilities; | ||
|
||
public class VersionTreeTests | ||
{ | ||
private static readonly JsonSerializerOptions _indentedJsonOption = new() { WriteIndented = true }; | ||
|
||
private static readonly List<Version> _versions = | ||
"1.0 1.2 1.2.1 1.2.2 1.2.3 1.3 1.4 1.4.1 1.4.1.1 1.4.1.2 1.4.1.3 1.4.2.1 1.4.2.2 1.4.2.4" | ||
.Split() | ||
.Select(Version.Parse) | ||
.ToList(); | ||
|
||
[Fact] | ||
public void VersionTreeShouldHaveExpectedStructure() | ||
{ | ||
var tree = VersionTree.Create(_versions); | ||
tree.Versions.ShouldBe(_versions); | ||
|
||
ShouldMatchJson(tree, File.ReadAllText("VersionTreeTests.FullStructure.json")); | ||
} | ||
|
||
[Fact] | ||
public void IndexingByNumberAndVersionShouldWork() | ||
{ | ||
var tree = VersionTree.Create(_versions); | ||
var expectedRevision = new Version(1, 4, 1, 1); | ||
|
||
tree[1]![4]![1]![1]!.Versions.ShouldBe([expectedRevision]); | ||
tree[expectedRevision]!.Versions.ShouldBe([expectedRevision]); | ||
} | ||
|
||
[Fact] | ||
public void VersionSubtreeShouldHaveExpectedStructure() | ||
{ | ||
var tree = VersionTree.Create(_versions); | ||
var expectedSubtree = File.ReadAllText("VersionTreeTests.Subtree.json"); | ||
|
||
// Verify full subtree structure. | ||
ShouldMatchJson(tree[1]![4], expectedSubtree); | ||
|
||
// Verify that indexing -1 results in the same. | ||
ShouldMatchJson(tree[1]![4]![-1]![-1], expectedSubtree); | ||
ShouldMatchJson(tree[new Version(1, 4)], expectedSubtree); | ||
} | ||
|
||
private static void ShouldMatchJson(VersionTree tree, string expectedJson) | ||
{ | ||
var actualJson = JsonSerializer.Serialize(tree, _indentedJsonOption); | ||
(string.Join(string.Empty, actualJson.Split()) == string.Join(string.Empty, expectedJson.Split())) | ||
.ShouldBeTrue($"Actual JSON:\n{actualJson}\nExpected JSON: {expectedJson}\n(whitespace does not matter)"); | ||
} | ||
} |
149 changes: 149 additions & 0 deletions
149
Lombiq.HelpfulLibraries.Tests/VersionTreeTests.FullStructure.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
{ | ||
"Versions": [ | ||
"1.0", | ||
"1.2", | ||
"1.2.1", | ||
"1.2.2", | ||
"1.2.3", | ||
"1.3", | ||
"1.4", | ||
"1.4.1", | ||
"1.4.1.1", | ||
"1.4.1.2", | ||
"1.4.1.3", | ||
"1.4.2.1", | ||
"1.4.2.2", | ||
"1.4.2.4" | ||
], | ||
"SubVersions": { | ||
"1": { | ||
"Versions": [ | ||
"1.0", | ||
"1.2", | ||
"1.2.1", | ||
"1.2.2", | ||
"1.2.3", | ||
"1.3", | ||
"1.4", | ||
"1.4.1", | ||
"1.4.1.1", | ||
"1.4.1.2", | ||
"1.4.1.3", | ||
"1.4.2.1", | ||
"1.4.2.2", | ||
"1.4.2.4" | ||
], | ||
"SubVersions": { | ||
"0": { | ||
"Versions": [ | ||
"1.0" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"2": { | ||
"Versions": [ | ||
"1.2", | ||
"1.2.1", | ||
"1.2.2", | ||
"1.2.3" | ||
], | ||
"SubVersions": { | ||
"1": { | ||
"Versions": [ | ||
"1.2.1" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"2": { | ||
"Versions": [ | ||
"1.2.2" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"3": { | ||
"Versions": [ | ||
"1.2.3" | ||
], | ||
"SubVersions": {} | ||
} | ||
} | ||
}, | ||
"3": { | ||
"Versions": [ | ||
"1.3" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"4": { | ||
"Versions": [ | ||
"1.4", | ||
"1.4.1", | ||
"1.4.1.1", | ||
"1.4.1.2", | ||
"1.4.1.3", | ||
"1.4.2.1", | ||
"1.4.2.2", | ||
"1.4.2.4" | ||
], | ||
"SubVersions": { | ||
"1": { | ||
"Versions": [ | ||
"1.4.1", | ||
"1.4.1.1", | ||
"1.4.1.2", | ||
"1.4.1.3" | ||
], | ||
"SubVersions": { | ||
"1": { | ||
"Versions": [ | ||
"1.4.1.1" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"2": { | ||
"Versions": [ | ||
"1.4.1.2" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"3": { | ||
"Versions": [ | ||
"1.4.1.3" | ||
], | ||
"SubVersions": {} | ||
} | ||
} | ||
}, | ||
"2": { | ||
"Versions": [ | ||
"1.4.2.1", | ||
"1.4.2.2", | ||
"1.4.2.4" | ||
], | ||
"SubVersions": { | ||
"1": { | ||
"Versions": [ | ||
"1.4.2.1" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"2": { | ||
"Versions": [ | ||
"1.4.2.2" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"4": { | ||
"Versions": [ | ||
"1.4.2.4" | ||
], | ||
"SubVersions": {} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} |
69 changes: 69 additions & 0 deletions
69
Lombiq.HelpfulLibraries.Tests/VersionTreeTests.Subtree.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,69 @@ | ||
{ | ||
"Versions": [ | ||
"1.4", | ||
"1.4.1", | ||
"1.4.1.1", | ||
"1.4.1.2", | ||
"1.4.1.3", | ||
"1.4.2.1", | ||
"1.4.2.2", | ||
"1.4.2.4" | ||
], | ||
"SubVersions": { | ||
"1": { | ||
"Versions": [ | ||
"1.4.1", | ||
"1.4.1.1", | ||
"1.4.1.2", | ||
"1.4.1.3" | ||
], | ||
"SubVersions": { | ||
"1": { | ||
"Versions": [ | ||
"1.4.1.1" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"2": { | ||
"Versions": [ | ||
"1.4.1.2" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"3": { | ||
"Versions": [ | ||
"1.4.1.3" | ||
], | ||
"SubVersions": {} | ||
} | ||
} | ||
}, | ||
"2": { | ||
"Versions": [ | ||
"1.4.2.1", | ||
"1.4.2.2", | ||
"1.4.2.4" | ||
], | ||
"SubVersions": { | ||
"1": { | ||
"Versions": [ | ||
"1.4.2.1" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"2": { | ||
"Versions": [ | ||
"1.4.2.2" | ||
], | ||
"SubVersions": {} | ||
}, | ||
"4": { | ||
"Versions": [ | ||
"1.4.2.4" | ||
], | ||
"SubVersions": {} | ||
} | ||
} | ||
} | ||
} | ||
} |