Skip to content

Commit

Permalink
Merge pull request #201 from SceneGate/feature/194-transform-children
Browse files Browse the repository at this point in the history
✨  Implement extension methods to transform node collections
  • Loading branch information
pleonex committed Nov 24, 2023
2 parents c820aac + b3bee29 commit 38e4d7b
Show file tree
Hide file tree
Showing 3 changed files with 339 additions and 0 deletions.
17 changes: 17 additions & 0 deletions docs/articles/core/virtual-file-system/nodes.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,23 @@ fluent-like for chaining conversions.

[!code-csharp[transform chaining](./../../../../src/Yarhl.Examples/FileSystem/NodeExamples.cs?name=TransformChain)]

### Transform collections

You can also apply a converter to a collection of nodes by using the following
extension methods:

- `IEnumerable<Node>.TransformWith`: use the converter with all the nodes and
return an `IEnumerable`.
- Note that this has the same behavior as an `IEnumerable`: **the conversion
will not happen until the collection is iterated**.
- `NavigableNodeCollection<Node>.TransformCollectionWith`: use the converter
with all the nodes and returns the same collection.
- The conversion happens immediately.

Only the overload that takes a converter instance `TransformWith(IConverter)`
will re-use the same converter for every node. The other overloads will create a
new converter for each node.

## Tags

Nodes can store additional metadata via the generic dictionary `Tags`. Each tag
Expand Down
181 changes: 181 additions & 0 deletions src/Yarhl.UnitTests/FileSystem/NodeExtensionsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
// Copyright (c) 2023 SceneGate

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
namespace Yarhl.UnitTests.FileSystem;

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using FluentAssertions;
using NUnit.Framework;
using Yarhl.FileSystem;
using Yarhl.UnitTests.FileFormat;

[TestFixture]
public class NodeExtensionsTests
{
[Test]
public void TransformWithGeneric()
{
int[] expected = new int[] { 1, 2, 3 };
using var parent = new Node("parent");
parent.Add(new Node("node1", new StringFormat("1")));
parent.Add(new Node("node2", new StringFormat("2")));
parent.Add(new Node("node3", new StringFormat("3")));

NavigableNodeCollection<Node> output = parent.Children
.TransformCollectionWith<StringFormat2IntFormat>();

// Nodes already transformed, before any iteration
Assert.That(parent.Children[0].Format, Is.TypeOf<IntFormat>());

Assert.That(output, Is.TypeOf<NavigableNodeCollection<Node>>());
Assert.That(output, Is.SameAs(parent.Children));
Assert.That(output.Select(n => n.Format), Has.All.TypeOf<IntFormat>());
Assert.That(
output.Select(n => n.GetFormatAs<IntFormat>().Value),
Is.EquivalentTo(expected));
}

[Test]
public void TransformWithGenericIEnumerable()
{
int[] expected = new int[] { 1, 2, 3 };
using var parent = new Node("parent");
parent.Add(new Node("node1", new StringFormat("1")));
parent.Add(new Node("node2", new StringFormat("2")));
parent.Add(new Node("node3", new StringFormat("3")));

IEnumerable<Node> outputEnumerable = parent.Children
.Where(n => true) // just to get an IEnumerable
.TransformWith<StringFormat2IntFormat>();
Assert.That(outputEnumerable, Is.InstanceOf<IEnumerable<Node>>());

// Nodes not transformed yet until is iterated
Assert.That(parent.Children[0].Format, Is.TypeOf<StringFormat>());

// Convert so iterating for the tests don't run it twice
Node[] output = outputEnumerable.ToArray();
Assert.That(output.Select(n => n.Format), Has.All.TypeOf<IntFormat>());
Assert.That(
output.Select(n => n.GetFormatAs<IntFormat>().Value),
Is.EquivalentTo(expected));
}

[Test]
public void TransformWithType()
{
int[] expected = new int[] { 0xC0, 0xC1, 0xC2 };
using var parent = new Node("parent");
parent.Add(new Node("node1", new StringFormat("C1")));
parent.Add(new Node("node2", new StringFormat("C2")));
parent.Add(new Node("node3", new StringFormat("C3")));

NavigableNodeCollection<Node> output = parent.Children.TransformCollectionWith(
typeof(StringFormatConverterWithConstructor),
NumberStyles.HexNumber,
-1);

// Nodes already transformed, before any iteration
Assert.That(parent.Children[0].Format, Is.TypeOf<IntFormat>());

Assert.That(output, Is.TypeOf<NavigableNodeCollection<Node>>());
Assert.That(output, Is.SameAs(parent.Children));
Assert.That(output.Select(n => n.Format), Has.All.TypeOf<IntFormat>());
Assert.That(
output.Select(n => n.GetFormatAs<IntFormat>().Value),
Is.EquivalentTo(expected));
}

[Test]
public void TransformWithTypeIEnumerable()
{
int[] expected = new int[] { 0xC0, 0xC1, 0xC2 };
using var parent = new Node("parent");
parent.Add(new Node("node1", new StringFormat("C1")));
parent.Add(new Node("node2", new StringFormat("C2")));
parent.Add(new Node("node3", new StringFormat("C3")));

IEnumerable<Node> outputEnumerable = parent.Children
.Where(n => true) // just to get an IEnumerable
.TransformWith(typeof(StringFormatConverterWithConstructor), NumberStyles.HexNumber, -1);
Assert.That(outputEnumerable, Is.InstanceOf<IEnumerable<Node>>());

// Nodes not transformed yet until is iterated
Assert.That(parent.Children[0].Format, Is.TypeOf<StringFormat>());

// Convert so iterating for the tests don't run it twice
Node[] output = outputEnumerable.ToArray();
Assert.That(output.Select(n => n.Format), Has.All.TypeOf<IntFormat>());
Assert.That(
output.Select(n => n.GetFormatAs<IntFormat>().Value),
Is.EquivalentTo(expected));
}

[Test]
public void TransformWithInstance()
{
var converter = new StringFormat2IntFormat();

int[] expected = new int[] { 1, 2, 3 };
using var parent = new Node("parent");
parent.Add(new Node("node1", new StringFormat("1")));
parent.Add(new Node("node2", new StringFormat("2")));
parent.Add(new Node("node3", new StringFormat("3")));

NavigableNodeCollection<Node> output = parent.Children.TransformCollectionWith(converter);

// Nodes already transformed, before any iteration
Assert.That(parent.Children[0].Format, Is.TypeOf<IntFormat>());

Assert.That(output, Is.TypeOf<NavigableNodeCollection<Node>>());
Assert.That(output, Is.SameAs(parent.Children));
Assert.That(output.Select(n => n.Format), Has.All.TypeOf<IntFormat>());
Assert.That(
output.Select(n => n.GetFormatAs<IntFormat>().Value),
Is.EquivalentTo(expected));
}

[Test]
public void TransformWithInstanceIEnumerable()
{
var converter = new StringFormat2IntFormat();

int[] expected = new int[] { 1, 2, 3 };
using var parent = new Node("parent");
parent.Add(new Node("node1", new StringFormat("1")));
parent.Add(new Node("node2", new StringFormat("2")));
parent.Add(new Node("node3", new StringFormat("3")));

IEnumerable<Node> outputEnumerable = parent.Children
.Where(n => true) // just to get an IEnumerable
.TransformWith(converter);
Assert.That(outputEnumerable, Is.InstanceOf<IEnumerable<Node>>());

// Nodes not transformed yet until is iterated
Assert.That(parent.Children[0].Format, Is.TypeOf<StringFormat>());

// Convert so iterating for the tests don't run it twice
Node[] output = outputEnumerable.ToArray();
Assert.That(output.Select(n => n.Format), Has.All.TypeOf<IntFormat>());
Assert.That(
output.Select(n => n.GetFormatAs<IntFormat>().Value),
Is.EquivalentTo(expected));
}
}
141 changes: 141 additions & 0 deletions src/Yarhl/FileSystem/NodeExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright (c) 2023 SceneGate

// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:

// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.

// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
namespace Yarhl.FileSystem;

using System;
using System.Collections.Generic;
using System.Linq;
using Yarhl.FileFormat;

/// <summary>
/// Extension methods for nodes.
/// </summary>
public static class NodeExtensions
{
/// <summary>
/// Iterate and transform the nodes in the collection with the given converter.
/// </summary>
/// <typeparam name="TConv">The type of the converter.</typeparam>
/// <param name="nodes">The collection of nodes to transform.</param>
/// <returns>The same collection.</returns>
/// <remarks>
/// It creates a new instance of the converter for each node.
/// It performs the conversion inmediately, not IEnumerable-styled.
/// </remarks>
public static NavigableNodeCollection<Node> TransformCollectionWith<TConv>(this NavigableNodeCollection<Node> nodes)
where TConv : IConverter, new()
{
foreach (Node node in nodes) {
_ = node.TransformWith<TConv>();
}

return nodes;
}

/// <summary>
/// Iterate and transform the nodes in the collection with the given converter.
/// </summary>
/// <returns>This same collection.</returns>
/// <param name="nodes">The collection of nodes to transform.</param>
/// <param name="converterType">The type of the converter to use.</param>
/// <param name="args">
/// Arguments for the constructor of the type if any.
/// </param>
/// <remarks>
/// It creates a new instance of the converter for each node.
/// </remarks>
public static NavigableNodeCollection<Node> TransformCollectionWith(
this NavigableNodeCollection<Node> nodes,
Type converterType,
params object?[] args)
{
foreach (Node node in nodes) {
_ = node.TransformWith(converterType, args);
}

return nodes;
}

/// <summary>
/// Iterate and transform the nodes in the collection with the given converter.
/// </summary>
/// <param name="nodes">The collection of nodes to transform.</param>
/// <param name="converter">Convert to use.</param>
/// <returns>The same collection.</returns>
/// <remarks>It re-uses the same converter instance for each node.</remarks>
public static NavigableNodeCollection<Node> TransformCollectionWith(
this NavigableNodeCollection<Node> nodes,
IConverter converter)
{
foreach (Node node in nodes) {
_ = node.TransformWith(converter);
}

return nodes;
}

/// <summary>
/// Creates a new IEnumerable to transform the nodes with the given converter.
/// </summary>
/// <typeparam name="TConv">The type of the converter.</typeparam>
/// <param name="nodes">The collection of nodes to transform.</param>
/// <returns>The same collection.</returns>
/// <remarks>
/// It creates a new instance of the converter for each node.
/// It returns a new IEnumerable and will run the conversion when iterated.
/// </remarks>
public static IEnumerable<Node> TransformWith<TConv>(this IEnumerable<Node> nodes)
where TConv : IConverter, new()
{
return nodes.Select(n => n.TransformWith<TConv>());
}

/// <summary>
/// Creates a new IEnumerable to transform the nodes with the given converter.
/// </summary>
/// <returns>This same collection.</returns>
/// <param name="nodes">The collection of nodes to transform.</param>
/// <param name="converterType">The type of the converter to use.</param>
/// <param name="args">
/// Arguments for the constructor of the type if any.
/// </param>
/// <remarks>
/// It creates a new instance of the converter for each node.
/// </remarks>
public static IEnumerable<Node> TransformWith(
this IEnumerable<Node> nodes,
Type converterType,
params object?[] args)
{
return nodes.Select(n => n.TransformWith(converterType, args));
}

/// <summary>
/// Creates a new IEnumerable to transform the nodes with the given converter.
/// </summary>
/// <param name="nodes">The collection of nodes to transform.</param>
/// <param name="converter">Convert to use.</param>
/// <returns>The same collection.</returns>
/// <remarks>It re-uses the same converter instance for each node.</remarks>
public static IEnumerable<Node> TransformWith(this IEnumerable<Node> nodes, IConverter converter)
{
return nodes.Select(n => n.TransformWith(converter));
}
}

0 comments on commit 38e4d7b

Please sign in to comment.