diff --git a/docs/articles/core/virtual-file-system/nodes.md b/docs/articles/core/virtual-file-system/nodes.md index 2b64a563..b4ee52e3 100644 --- a/docs/articles/core/virtual-file-system/nodes.md +++ b/docs/articles/core/virtual-file-system/nodes.md @@ -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.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.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 diff --git a/src/Yarhl.UnitTests/FileSystem/NodeExtensionsTests.cs b/src/Yarhl.UnitTests/FileSystem/NodeExtensionsTests.cs new file mode 100644 index 00000000..a19c420b --- /dev/null +++ b/src/Yarhl.UnitTests/FileSystem/NodeExtensionsTests.cs @@ -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 output = parent.Children + .TransformCollectionWith(); + + // Nodes already transformed, before any iteration + Assert.That(parent.Children[0].Format, Is.TypeOf()); + + Assert.That(output, Is.TypeOf>()); + Assert.That(output, Is.SameAs(parent.Children)); + Assert.That(output.Select(n => n.Format), Has.All.TypeOf()); + Assert.That( + output.Select(n => n.GetFormatAs().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 outputEnumerable = parent.Children + .Where(n => true) // just to get an IEnumerable + .TransformWith(); + Assert.That(outputEnumerable, Is.InstanceOf>()); + + // Nodes not transformed yet until is iterated + Assert.That(parent.Children[0].Format, Is.TypeOf()); + + // 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()); + Assert.That( + output.Select(n => n.GetFormatAs().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 output = parent.Children.TransformCollectionWith( + typeof(StringFormatConverterWithConstructor), + NumberStyles.HexNumber, + -1); + + // Nodes already transformed, before any iteration + Assert.That(parent.Children[0].Format, Is.TypeOf()); + + Assert.That(output, Is.TypeOf>()); + Assert.That(output, Is.SameAs(parent.Children)); + Assert.That(output.Select(n => n.Format), Has.All.TypeOf()); + Assert.That( + output.Select(n => n.GetFormatAs().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 outputEnumerable = parent.Children + .Where(n => true) // just to get an IEnumerable + .TransformWith(typeof(StringFormatConverterWithConstructor), NumberStyles.HexNumber, -1); + Assert.That(outputEnumerable, Is.InstanceOf>()); + + // Nodes not transformed yet until is iterated + Assert.That(parent.Children[0].Format, Is.TypeOf()); + + // 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()); + Assert.That( + output.Select(n => n.GetFormatAs().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 output = parent.Children.TransformCollectionWith(converter); + + // Nodes already transformed, before any iteration + Assert.That(parent.Children[0].Format, Is.TypeOf()); + + Assert.That(output, Is.TypeOf>()); + Assert.That(output, Is.SameAs(parent.Children)); + Assert.That(output.Select(n => n.Format), Has.All.TypeOf()); + Assert.That( + output.Select(n => n.GetFormatAs().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 outputEnumerable = parent.Children + .Where(n => true) // just to get an IEnumerable + .TransformWith(converter); + Assert.That(outputEnumerable, Is.InstanceOf>()); + + // Nodes not transformed yet until is iterated + Assert.That(parent.Children[0].Format, Is.TypeOf()); + + // 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()); + Assert.That( + output.Select(n => n.GetFormatAs().Value), + Is.EquivalentTo(expected)); + } +} diff --git a/src/Yarhl/FileSystem/NodeExtensions.cs b/src/Yarhl/FileSystem/NodeExtensions.cs new file mode 100644 index 00000000..b3282eb1 --- /dev/null +++ b/src/Yarhl/FileSystem/NodeExtensions.cs @@ -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; + +/// +/// Extension methods for nodes. +/// +public static class NodeExtensions +{ + /// + /// Iterate and transform the nodes in the collection with the given converter. + /// + /// The type of the converter. + /// The collection of nodes to transform. + /// The same collection. + /// + /// It creates a new instance of the converter for each node. + /// It performs the conversion inmediately, not IEnumerable-styled. + /// + public static NavigableNodeCollection TransformCollectionWith(this NavigableNodeCollection nodes) + where TConv : IConverter, new() + { + foreach (Node node in nodes) { + _ = node.TransformWith(); + } + + return nodes; + } + + /// + /// Iterate and transform the nodes in the collection with the given converter. + /// + /// This same collection. + /// The collection of nodes to transform. + /// The type of the converter to use. + /// + /// Arguments for the constructor of the type if any. + /// + /// + /// It creates a new instance of the converter for each node. + /// + public static NavigableNodeCollection TransformCollectionWith( + this NavigableNodeCollection nodes, + Type converterType, + params object?[] args) + { + foreach (Node node in nodes) { + _ = node.TransformWith(converterType, args); + } + + return nodes; + } + + /// + /// Iterate and transform the nodes in the collection with the given converter. + /// + /// The collection of nodes to transform. + /// Convert to use. + /// The same collection. + /// It re-uses the same converter instance for each node. + public static NavigableNodeCollection TransformCollectionWith( + this NavigableNodeCollection nodes, + IConverter converter) + { + foreach (Node node in nodes) { + _ = node.TransformWith(converter); + } + + return nodes; + } + + /// + /// Creates a new IEnumerable to transform the nodes with the given converter. + /// + /// The type of the converter. + /// The collection of nodes to transform. + /// The same collection. + /// + /// It creates a new instance of the converter for each node. + /// It returns a new IEnumerable and will run the conversion when iterated. + /// + public static IEnumerable TransformWith(this IEnumerable nodes) + where TConv : IConverter, new() + { + return nodes.Select(n => n.TransformWith()); + } + + /// + /// Creates a new IEnumerable to transform the nodes with the given converter. + /// + /// This same collection. + /// The collection of nodes to transform. + /// The type of the converter to use. + /// + /// Arguments for the constructor of the type if any. + /// + /// + /// It creates a new instance of the converter for each node. + /// + public static IEnumerable TransformWith( + this IEnumerable nodes, + Type converterType, + params object?[] args) + { + return nodes.Select(n => n.TransformWith(converterType, args)); + } + + /// + /// Creates a new IEnumerable to transform the nodes with the given converter. + /// + /// The collection of nodes to transform. + /// Convert to use. + /// The same collection. + /// It re-uses the same converter instance for each node. + public static IEnumerable TransformWith(this IEnumerable nodes, IConverter converter) + { + return nodes.Select(n => n.TransformWith(converter)); + } +}