diff --git a/docs/articles/core/virtual-file-system/nodes.md b/docs/articles/core/virtual-file-system/nodes.md index fa716e4c..2b64a563 100644 --- a/docs/articles/core/virtual-file-system/nodes.md +++ b/docs/articles/core/virtual-file-system/nodes.md @@ -51,6 +51,20 @@ To access to a child use its index, `Children[3]`, or its name, > You can find more ways to iterate or navigate nodes across a hierarchy in the > [`Navigator`](xref:Yarhl.FileSystem.Navigator) class. +### Path + +The [`Path`](xref:Yarhl.FileSystem.NavigableNode`1.Path) property provides the +full route to the node. It contains the name of all its parent from the root +node to the node. It does include the node name. + +The path separator is constant for every OS. It's defined in +[`NodeSystem.PathSeparator`](xref:Yarhl.FileSystem.NodeSystem.PathSeparator). + +> [!WARNING] +> Nodes may change their name. If a _parent_ node change its name, the path of +> all its children will change as well. Be careful storing the path, for +> instance as a key, as it may change. + ### Add or remove It's possible to add or remove children from its parent node. Use the method diff --git a/src/Yarhl.UnitTests/FileSystem/NavigableNodeTests.cs b/src/Yarhl.UnitTests/FileSystem/NavigableNodeTests.cs index 9bac5fa3..a2f7518e 100644 --- a/src/Yarhl.UnitTests/FileSystem/NavigableNodeTests.cs +++ b/src/Yarhl.UnitTests/FileSystem/NavigableNodeTests.cs @@ -45,6 +45,10 @@ public void ExceptionIfInvalidName() Assert.That( () => new DummyNavigable(string.Empty), Throws.TypeOf()); + + using var node = new DummyNavigable("name"); + Assert.That(() => node.Name = null, Throws.ArgumentNullException); + Assert.That(() => node.Name = string.Empty, Throws.ArgumentNullException); } [Test] @@ -55,6 +59,9 @@ public void ExceptionIfInvalidCharacters() Assert.That( ex.Message, Contains.Substring("Name contains invalid characters")); + + using var node = new DummyNavigable("name"); + Assert.That(() => node.Name = "MyT/est", Throws.ArgumentException); } [Test] @@ -62,6 +69,20 @@ public void NameProperty() { using var node = new DummyNavigable("MyNameTest"); Assert.AreEqual("MyNameTest", node.Name); + + node.Name = "MyNewName"; + Assert.That(node.Name, Is.EqualTo("MyNewName")); + } + + [Test] + public void RenamingWithParentChildMatchThrows() + { + using var parent = new DummyNavigable("parent"); + using var node1 = new DummyNavigable("node1"); + parent.Add(node1); + parent.Add(new DummyNavigable("node2")); + + Assert.That(() => node1.Name = "node2", Throws.ArgumentException); } [Test] @@ -82,6 +103,22 @@ public void PathWithParent() Assert.AreEqual("/MyParent", parentNode.Path); } + [Test] + public void RenamingChangesPath() + { + using var parent = new DummyNavigable("parent"); + using var node = new DummyNavigable("node1"); + parent.Add(node); + + Assert.That(node.Path, Is.EqualTo("/parent/node1")); + + parent.Name = "root"; + Assert.That(node.Path, Is.EqualTo("/root/node1")); + + node.Name = "child"; + Assert.That(node.Path, Is.EqualTo("/root/child")); + } + [Test] public void TagsAllowAdding() { diff --git a/src/Yarhl/FileSystem/NavigableNode.cs b/src/Yarhl/FileSystem/NavigableNode.cs index 286ac312..9886504f 100644 --- a/src/Yarhl/FileSystem/NavigableNode.cs +++ b/src/Yarhl/FileSystem/NavigableNode.cs @@ -21,6 +21,7 @@ namespace Yarhl.FileSystem { using System; using System.Collections.Generic; + using System.Diagnostics.CodeAnalysis; using System.Linq; /// @@ -30,8 +31,10 @@ namespace Yarhl.FileSystem public abstract class NavigableNode : IDisposable where T : NavigableNode { - readonly List children; - readonly DefaultNavigableNodeComparer defaultComparer = new DefaultNavigableNodeComparer(); + private readonly List children; + private readonly DefaultNavigableNodeComparer defaultComparer = new DefaultNavigableNodeComparer(); + + private string name; /// /// Initializes a new instance of the @@ -40,15 +43,6 @@ public abstract class NavigableNode : IDisposable /// Node name. protected NavigableNode(string name) { - if (string.IsNullOrEmpty(name)) - throw new ArgumentNullException(nameof(name)); - - if (name.Contains(NodeSystem.PathSeparator)) { - throw new ArgumentException( - "Name contains invalid characters", - nameof(name)); - } - Name = name; Tags = new Dictionary(); children = new List(); @@ -56,10 +50,31 @@ protected NavigableNode(string name) } /// - /// Gets the node name. + /// Gets or sets the node name. /// public string Name { - get; + get => name; + + [MemberNotNull(nameof(name))] + set { + if (string.IsNullOrEmpty(value)) { + throw new ArgumentNullException(nameof(value)); + } + + if (value.Contains(NodeSystem.PathSeparator)) { + throw new ArgumentException( + "Name contains invalid characters", + nameof(value)); + } + + if (Parent?.Children.Any(c => c != this && c.name == value) ?? false) { + throw new ArgumentException( + "Parent already contains a node with the desired name", + nameof(value)); + } + + name = value; + } } /// @@ -67,6 +82,8 @@ public string Name { /// /// /// It includes the names of all the parent nodes and this node. + /// As nodes may change their name, the path from children may + /// change at any time. Do not store it as keys. /// public string Path => (Parent?.Path ?? string.Empty) + NodeSystem.PathSeparator + Name;