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