diff --git a/src/Nitride.IO/Nitride.IO.csproj b/src/Nitride.IO/Nitride.IO.csproj index b72e74a..901c920 100644 --- a/src/Nitride.IO/Nitride.IO.csproj +++ b/src/Nitride.IO/Nitride.IO.csproj @@ -8,16 +8,16 @@ - - - - - - + + + + + + - + diff --git a/src/Nitride.IO/Paths/DirectChildEntityList.cs b/src/Nitride.IO/Paths/DirectChildEntityList.cs new file mode 100644 index 0000000..6c6c069 --- /dev/null +++ b/src/Nitride.IO/Paths/DirectChildEntityList.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; + +using Gallium; + +namespace Nitride.IO.Paths; + +/// +/// A wrapper around List<Entity> for handling direct children lists. +/// +public class DirectChildEntityList : List +{ + /// + public DirectChildEntityList() + { + } + + /// + public DirectChildEntityList(IEnumerable collection) + : base(collection) + { + } +} diff --git a/src/Nitride.IO/Paths/DirectChildPathScanner.cs b/src/Nitride.IO/Paths/DirectChildPathScanner.cs new file mode 100644 index 0000000..be173a9 --- /dev/null +++ b/src/Nitride.IO/Paths/DirectChildPathScanner.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; + +using FluentValidation; + +using Gallium; + +using Nitride.Entities; + +using Zio; + +namespace Nitride.IO.Paths; + +/// +/// Implements a scanner that gathers up all the direct child files +/// and folders underneath a given path. +/// +[WithProperties] +public partial class DirectChildPathScanner : EntityScanner +{ + /// + public DirectChildPathScanner(IValidator validator) + : base(validator) + { + this.GetKeysFromEntity = this.InternalGetKeysFromEntity; + } + + private IEnumerable? InternalGetKeysFromEntity(Entity entity) + { + // Get the path for the entity. If we don't have a path, then there + // is nothing to do. + if (!entity.TryGet(out UPath path)) + { + return null; + } + + // If we are a root path, then we don't have a parent. + if (path.IsEmpty || path.IsNull || path == "/") + { + return null; + } + + // If we are using directory indexes, skip when we have an index root. + // Otherwise, get the parent and use that as the key. + return path.GetDirectoryIndexPath() == "/" ? null : new[] { path.GetParentDirectoryIndexPath() }; + } +} diff --git a/src/Nitride.IO/Paths/LinkDirectChildren.cs b/src/Nitride.IO/Paths/LinkDirectChildren.cs new file mode 100644 index 0000000..fe3dabe --- /dev/null +++ b/src/Nitride.IO/Paths/LinkDirectChildren.cs @@ -0,0 +1,46 @@ +using System.Collections.Generic; +using System.Linq; + +using FluentValidation; + +using Gallium; + +using Nitride.Entities; + +using Serilog; + +using Zio; + +namespace Nitride.IO.Paths; + +[WithProperties] +public partial class LinkDirectChildren : CreateOrUpdateIndex +{ + /// + public LinkDirectChildren(ILogger logger, IValidator validator) + : base(logger, validator) + { + this.UpdateIndex = this.InternalUpdateIndex; + this.GetIndexKey = e => e.Get().GetDirectoryIndexPath(); + } + + /// + public override IEnumerable Run(IEnumerable input) + { + if (this.Scanner != null!) + { + return base.Run(input); + } + + this.Scanner = new DirectChildPathScanner(new EntityScannerValidator()); + + input = this.Scanner.Run(input).ToList(); + + return base.Run(input); + } + + private Entity InternalUpdateIndex(Entity entity, string _, IEnumerable list) + { + return entity.Add(new DirectChildEntityList(list)); + } +} diff --git a/src/Nitride/Entities/CreateOrUpdateIndex.cs b/src/Nitride/Entities/CreateOrUpdateIndex.cs index 113e676..6057fd6 100644 --- a/src/Nitride/Entities/CreateOrUpdateIndex.cs +++ b/src/Nitride/Entities/CreateOrUpdateIndex.cs @@ -36,9 +36,10 @@ public partial class CreateOrUpdateIndex : OperationBase /// /// Creates an index for a given key. This will not be called for any - /// index that has been already created. + /// index that has been already created. If this is null, no new indexes + /// will be made. /// - public Func, Entity> CreateIndex { get; set; } = null!; + public Func, Entity>? CreateIndex { get; set; } = null!; /// /// Gets or sets the function to retrieve the key from an existing @@ -94,14 +95,12 @@ public partial class CreateOrUpdateIndex : OperationBase } // Once we're done with the list, we need to create the missing indexes. - foreach (string? key in scanned.Keys) + if (this.CreateIndex != null) { - if (existing.Contains(key)) + foreach (string key in scanned.Keys.Where(key => !existing.Contains(key))) { - continue; + yield return this.CreateIndex(key, scanned[key]); } - - yield return this.CreateIndex(key, scanned[key]); } // Report the results. diff --git a/src/Nitride/Entities/CreateOrUpdateIndexValidator.cs b/src/Nitride/Entities/CreateOrUpdateIndexValidator.cs index 8b92ed1..4f64f60 100644 --- a/src/Nitride/Entities/CreateOrUpdateIndexValidator.cs +++ b/src/Nitride/Entities/CreateOrUpdateIndexValidator.cs @@ -8,7 +8,6 @@ public class CreateOrUpdateIndexValidator : AbstractValidator x.Scanner).NotNull(); this.RuleFor(x => x.GetIndexKey).NotNull(); - this.RuleFor(x => x.CreateIndex).NotNull(); this.RuleFor(x => x.UpdateIndex).NotNull(); } } diff --git a/tests/Nitride.IO.Tests/Nitride.IO.Tests.csproj b/tests/Nitride.IO.Tests/Nitride.IO.Tests.csproj index fe780a0..ab3a88b 100644 --- a/tests/Nitride.IO.Tests/Nitride.IO.Tests.csproj +++ b/tests/Nitride.IO.Tests/Nitride.IO.Tests.csproj @@ -6,20 +6,22 @@ - - + + - - - - + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - + runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/tests/Nitride.IO.Tests/NitrideIOTestBase.cs b/tests/Nitride.IO.Tests/NitrideIOTestBase.cs index 13031ff..614ca91 100644 --- a/tests/Nitride.IO.Tests/NitrideIOTestBase.cs +++ b/tests/Nitride.IO.Tests/NitrideIOTestBase.cs @@ -1,6 +1,14 @@ +using System; +using System.Text; + +using KellermanSoftware.CompareNetObjects; + using MfGames.TestSetup; +using Newtonsoft.Json; + using Xunit.Abstractions; +using Xunit.Sdk; namespace Nitride.IO.Tests; @@ -10,4 +18,39 @@ public abstract class NitrideIOTestBase : TestBase : base(output) { } + + protected void CompareObjects(T expected, T actual) + where T : class + { + CompareLogic compare = new() + { + Config = + { + MaxDifferences = int.MaxValue, + }, + }; + ComparisonResult comparison = compare.Compare(expected, actual); + + if (comparison.AreEqual) + { + return; + } + + // Format the error message. + StringBuilder message = new(); + + message.AppendLine("# Expected"); + message.AppendLine(); + message.AppendLine(JsonConvert.SerializeObject(expected, Formatting.Indented)); + message.AppendLine(); + message.Append("# Actual"); + message.AppendLine(); + message.AppendLine(JsonConvert.SerializeObject(actual, Formatting.Indented)); + message.AppendLine(); + message.Append("# Results"); + message.AppendLine(); + message.AppendLine(comparison.DifferencesString); + + throw new XunitException(message.ToString()); + } } diff --git a/tests/Nitride.IO.Tests/AddPathPrefixTest.cs b/tests/Nitride.IO.Tests/Paths/AddPathPrefixTest.cs similarity index 100% rename from tests/Nitride.IO.Tests/AddPathPrefixTest.cs rename to tests/Nitride.IO.Tests/Paths/AddPathPrefixTest.cs diff --git a/tests/Nitride.IO.Tests/Paths/DirectChildPathScannerTests.cs b/tests/Nitride.IO.Tests/Paths/DirectChildPathScannerTests.cs new file mode 100644 index 0000000..1368c87 --- /dev/null +++ b/tests/Nitride.IO.Tests/Paths/DirectChildPathScannerTests.cs @@ -0,0 +1,64 @@ +using System.Collections.Generic; +using System.Linq; + +using Nitride.IO.Contents; +using Nitride.IO.Paths; + +using Xunit; +using Xunit.Abstractions; + +using Zio; + +namespace Nitride.IO.Tests; + +public class DirectChildPathScannerTests : NitrideIOTestBase +{ + public DirectChildPathScannerTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void NestedFoldersTests() + { + // Set up the test. + using NitrideIOTestContext context = this.CreateContext(); + + // Set up the file. + IFileSystem fileSystem = context.FileSystem; + + fileSystem.CreateDirectory("/a"); + fileSystem.CreateDirectory("/b"); + fileSystem.CreateDirectory("/a/c"); + fileSystem.CreateDirectory("/a/d"); + fileSystem.CreateDirectory("/a/d/e"); + fileSystem.CreateFile("/index.md"); + fileSystem.CreateFile("/a/index.md"); + fileSystem.CreateFile("/b/index.md"); + fileSystem.CreateFile("/a/c/index.md"); + fileSystem.CreateFile("/a/d/index.md"); + fileSystem.CreateFile("/a/d/e/index.md"); + + // Set up the operation. + ReadFiles readFiles = context.Resolve(); + DirectChildPathScanner op = context.Resolve(); + + // Read and replace the paths. + var _ = readFiles.WithPattern("/**").Run().Run(op).ToList(); + KeyValuePair[] actual = op.GetScannedResults() + .ToDictionary(x => x.Key, x => x.Value.Select(y => y.Get().ToString()).ToArray()) + .ToList() + .OrderBy(x => x.Key) + .ToArray(); + + // Verify the results. + KeyValuePair[] expected = new[] + { + new KeyValuePair("/", new[] { "/a/index.md", "/b/index.md" }), + new KeyValuePair("/a/", new[] { "/a/c/index.md", "/a/d/index.md" }), + new KeyValuePair("/a/d/", new[] { "/a/d/e/index.md" }), + }; + + CompareObjects(expected, actual); + } +} diff --git a/tests/Nitride.IO.Tests/Paths/LinkDirectChildrenTests.cs b/tests/Nitride.IO.Tests/Paths/LinkDirectChildrenTests.cs new file mode 100644 index 0000000..5cfe5a7 --- /dev/null +++ b/tests/Nitride.IO.Tests/Paths/LinkDirectChildrenTests.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; + +using Nitride.Entities; +using Nitride.IO.Contents; +using Nitride.IO.Paths; + +using Xunit; +using Xunit.Abstractions; + +using Zio; + +namespace Nitride.IO.Tests; + +public class LinkDirectChildrenTests : NitrideIOTestBase +{ + public LinkDirectChildrenTests(ITestOutputHelper output) + : base(output) + { + } + + [Fact] + public void NestedLinkTest() + { + // Set up the test. + using NitrideIOTestContext context = this.CreateContext(); + + // Set up the file. + IFileSystem fileSystem = context.FileSystem; + + fileSystem.CreateDirectory("/a"); + fileSystem.CreateDirectory("/b"); + fileSystem.CreateDirectory("/a/c"); + fileSystem.CreateDirectory("/a/d"); + fileSystem.CreateDirectory("/a/d/e"); + fileSystem.CreateFile("/index.md"); + fileSystem.CreateFile("/a/index.md"); + fileSystem.CreateFile("/b/index.md"); + fileSystem.CreateFile("/a/c/index.md"); + fileSystem.CreateFile("/a/d/index.md"); + fileSystem.CreateFile("/a/d/e/index.md"); + + // Set up the operation. + ReadFiles readFiles = context.Resolve(); + DirectChildPathScanner scanner = context.Resolve(); + CreateOrUpdateIndex op = context.Resolve().WithScanner(scanner); + + // Read and replace the paths. + Tuple[]? actual = readFiles.WithPattern("/**") + .Run() + .Run(scanner) + .ToList() + .Run(op) + .Select( + x => new Tuple( + x.Get().ToString(), + x.GetOptional() + ?.Select(y => y.Get().ToString()) + .OrderBy(y => y) + .ToArray())) + .OrderBy(x => x.Item1) + .ToArray(); + + // Verify the results. + Tuple[] expected = new[] + { + new Tuple("/a/c/index.md", new string[] { }), + new Tuple("/a/d/e/index.md", new string[] { }), + new Tuple("/a/d/index.md", new[] { "/a/d/e/index.md" }), + new Tuple("/a/index.md", new[] { "/a/c/index.md", "/a/d/index.md" }), + new Tuple("/b/index.md", new string[] { }), + new Tuple("/index.md", new[] { "/a/index.md", "/b/index.md" }), + }; + + this.CompareObjects(expected, actual); + } +} diff --git a/tests/Nitride.IO.Tests/MoveToIndexPathsTest.cs b/tests/Nitride.IO.Tests/Paths/MoveToIndexPathsTest.cs similarity index 100% rename from tests/Nitride.IO.Tests/MoveToIndexPathsTest.cs rename to tests/Nitride.IO.Tests/Paths/MoveToIndexPathsTest.cs diff --git a/tests/Nitride.IO.Tests/RemovePathPrefixTest.cs b/tests/Nitride.IO.Tests/Paths/RemovePathPrefixTest.cs similarity index 100% rename from tests/Nitride.IO.Tests/RemovePathPrefixTest.cs rename to tests/Nitride.IO.Tests/Paths/RemovePathPrefixTest.cs