Skip to content

Commit

Permalink
Merge pull request #200 from SceneGate/feature/193-rename-node
Browse files Browse the repository at this point in the history
✨  Implement node renaming
  • Loading branch information
pleonex committed Nov 24, 2023
2 parents bf67ed3 + c5ac82e commit c820aac
Show file tree
Hide file tree
Showing 3 changed files with 81 additions and 13 deletions.
14 changes: 14 additions & 0 deletions docs/articles/core/virtual-file-system/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
37 changes: 37 additions & 0 deletions src/Yarhl.UnitTests/FileSystem/NavigableNodeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ public void ExceptionIfInvalidName()
Assert.That(
() => new DummyNavigable(string.Empty),
Throws.TypeOf<ArgumentNullException>());

using var node = new DummyNavigable("name");
Assert.That(() => node.Name = null, Throws.ArgumentNullException);
Assert.That(() => node.Name = string.Empty, Throws.ArgumentNullException);
}

[Test]
Expand All @@ -55,13 +59,30 @@ 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]
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]
Expand All @@ -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()
{
Expand Down
43 changes: 30 additions & 13 deletions src/Yarhl/FileSystem/NavigableNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ namespace Yarhl.FileSystem
{
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;

/// <summary>
Expand All @@ -30,8 +31,10 @@ namespace Yarhl.FileSystem
public abstract class NavigableNode<T> : IDisposable
where T : NavigableNode<T>
{
readonly List<T> children;
readonly DefaultNavigableNodeComparer defaultComparer = new DefaultNavigableNodeComparer();
private readonly List<T> children;
private readonly DefaultNavigableNodeComparer defaultComparer = new DefaultNavigableNodeComparer();

private string name;

/// <summary>
/// Initializes a new instance of the
Expand All @@ -40,33 +43,47 @@ public abstract class NavigableNode<T> : IDisposable
/// <param name="name">Node name.</param>
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<string, dynamic>();
children = new List<T>();
Children = new NavigableNodeCollection<T>(children);
}

/// <summary>
/// Gets the node name.
/// Gets or sets the node name.
/// </summary>
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;
}
}

/// <summary>
/// Gets the path.
/// </summary>
/// <remarks>
/// <para>It includes the names of all the parent nodes and this node.</para>
/// <para>As nodes may change their name, the path from children may
/// change at any time. Do not store it as keys.</para>
/// </remarks>
public string Path => (Parent?.Path ?? string.Empty) + NodeSystem.PathSeparator + Name;

Expand Down

0 comments on commit c820aac

Please sign in to comment.