Skip to content

Commit

Permalink
Merge pull request #168 from Kaplas80/feature/cloneable
Browse files Browse the repository at this point in the history
Implement ICloneableFormat
  • Loading branch information
pleonex committed May 8, 2021
2 parents 7d1b642 + 905113a commit 287b4b9
Show file tree
Hide file tree
Showing 13 changed files with 322 additions and 3 deletions.
18 changes: 17 additions & 1 deletion src/Yarhl.Media/Text/Po.cs
Expand Up @@ -27,7 +27,7 @@ namespace Yarhl.Media.Text
/// <summary>
/// Portable Object format for translations.
/// </summary>
public class Po : IFormat
public class Po : ICloneableFormat
{
readonly IList<PoEntry> entries;
readonly ReadOnlyCollection<PoEntry> readonlyEntries;
Expand Down Expand Up @@ -134,6 +134,22 @@ public PoEntry FindEntry(string original, string context = null)
return searchEntries.ContainsKey(key) ? searchEntries[key] : null;
}

/// <inheritdoc />
public virtual object DeepClone()
{
Po clone = new Po();
if (header != null) {
clone.header = new PoHeader(header);
}

foreach (PoEntry entry in entries)
{
clone.Add(new PoEntry(entry));
}

return clone;
}

static string GetKey(PoEntry entry)
{
return GetKey(entry.Original, entry.Context);
Expand Down
22 changes: 22 additions & 0 deletions src/Yarhl.Media/Text/PoEntry.cs
Expand Up @@ -19,6 +19,8 @@
// SOFTWARE.
namespace Yarhl.Media.Text
{
using System;

/// <summary>
/// Entry in PO formats. Represents a translation unit.
/// </summary>
Expand All @@ -43,6 +45,26 @@ public PoEntry(string original)
Translated = string.Empty;
}

/// <summary>
/// Initializes a new instance of the <see cref="PoEntry"/> class.
/// </summary>
/// <param name="entry">The entry to copy.</param>
public PoEntry(PoEntry entry)
{
if (entry == null)
throw new ArgumentNullException(nameof(entry));

Original = entry.Original;
Translated = entry.Translated;
Context = entry.Context;
TranslatorComment = entry.TranslatorComment;
ExtractedComments = entry.ExtractedComments;
Reference = entry.Reference;
Flags = entry.Flags;
PreviousContext = entry.PreviousContext;
PreviousOriginal = entry.PreviousOriginal;
}

/// <summary>
/// Gets or sets the original content to translate.
/// </summary>
Expand Down
24 changes: 24 additions & 0 deletions src/Yarhl.Media/Text/PoHeader.cs
Expand Up @@ -50,6 +50,30 @@ public PoHeader(string id, string reporter, string lang)
CreationDate = DateTime.Now.ToShortDateString();
}

/// <summary>
/// Initializes a new instance of the <see cref="PoHeader"/> class.
/// </summary>
/// <param name="header">The header to copy.</param>
public PoHeader(PoHeader header)
: this()
{
if (header == null)
throw new ArgumentNullException(nameof(header));

ProjectIdVersion = header.ProjectIdVersion;
ReportMsgidBugsTo = header.ReportMsgidBugsTo;
Language = header.Language;
CreationDate = header.CreationDate;
RevisionDate = header.RevisionDate;
LastTranslator = header.LastTranslator;
LanguageTeam = header.LanguageTeam;
PluralForms = header.PluralForms;
foreach (KeyValuePair<string, string> extension in header.Extensions)
{
Extensions[extension.Key] = extension.Value;
}
}

/// <summary>
/// Gets the type of the content and encoding.
/// </summary>
Expand Down
25 changes: 25 additions & 0 deletions src/Yarhl.UnitTests/FileSystem/NodeContainerFormatTests.cs
Expand Up @@ -183,6 +183,31 @@ public void MoveChildrenToNodeCanMergeContainers()
Assert.That(folder3.Children.Count, Is.Zero);
}

[Test]
public void Clone()
{
using NodeContainerFormat format = new NodeContainerFormat();

using Node child1 = NodeFactory.CreateContainer("child1");
using Node child2 = new Node("child2");
using Node grandchild = new Node("grandchild");

format.Root.Add(child1);
format.Root.Add(child2);
child1.Add(grandchild);

using NodeContainerFormat clone = (NodeContainerFormat)format.DeepClone();

Node child1Clone = clone.Root.Children["child1"];
Node child2Clone = clone.Root.Children["child2"];
Node grandchildClone = child1Clone.Children["grandchild"];

Assert.AreNotSame(format, clone);
Assert.AreNotSame(child1, child1Clone);
Assert.AreNotSame(child2, child2Clone);
Assert.AreNotSame(grandchild, grandchildClone);
}

protected override NodeContainerFormat CreateDummyFormat()
{
return new NodeContainerFormat();
Expand Down
36 changes: 36 additions & 0 deletions src/Yarhl.UnitTests/FileSystem/NodeTests.cs
Expand Up @@ -667,6 +667,42 @@ public void DisposeTwiceDoesNotThrow()
Assert.DoesNotThrow(node.Dispose);
}

[Test]
public void CloneNullNodeThrowsException()
{
Assert.Throws<ArgumentNullException>(() => _ = new Node((Node)null));
}

[Test]
public void NotCloneableFormatThrowsException()
{
using Node node = new Node("test", new StringFormatTest("3"));
Assert.Throws<InvalidOperationException>(() => _ = new Node(node));
}

[Test]
public void CloneNodeHasTags()
{
using Node node = NodeFactory.FromMemory("test");
node.Tags["TestTag"] = 23;

using Node clone = new Node(node);

Assert.AreNotSame(node, clone);
Assert.AreEqual(1, clone.Tags.Count);
Assert.AreEqual(23, clone.Tags["TestTag"]);
}

[Test]
public void CloneNullFormatNode()
{
using Node node = new Node("test");
using Node clone = new Node(node);

Assert.AreNotSame(node, clone);
Assert.IsNull(clone.Format);
}

[PartNotDiscoverable]
public class PrivateConverter :
IConverter<StringFormatTest, IntFormatTest>,
Expand Down
13 changes: 13 additions & 0 deletions src/Yarhl.UnitTests/IO/BinaryFormatTests.cs
Expand Up @@ -180,6 +180,19 @@ public void MemoryConstructor()
format.Dispose();
}

[Test]
public void Clone()
{
byte[] data = { 0x01, 0x02, 0x03 };
using DataStream stream = DataStreamFactory.FromArray(data, 0, data.Length);
using BinaryFormat format = new BinaryFormat(stream);

using BinaryFormat clone = (BinaryFormat)format.DeepClone();

Assert.AreNotSame(format, clone);
Assert.IsTrue(format.Stream.Compare(clone.Stream));
}

protected override BinaryFormat CreateDummyFormat()
{
using DataStream stream = new DataStream();
Expand Down
36 changes: 36 additions & 0 deletions src/Yarhl.UnitTests/Media/Text/PoEntryTests.cs
Expand Up @@ -19,6 +19,7 @@
// SOFTWARE.
namespace Yarhl.UnitTests.Media.Text
{
using System;
using NUnit.Framework;
using Yarhl.Media.Text;

Expand Down Expand Up @@ -81,5 +82,40 @@ public void TextShowsOriginalOrTranslated()
entry.Translated = "translated";
Assert.That(entry.Text, Is.EqualTo(entry.Translated));
}

[Test]
public void CloneNullThrowsException()
{
Assert.Throws<ArgumentNullException>(() => _ = new PoEntry((PoEntry)null));
}

[Test]
public void Clone()
{
PoEntry entry = new PoEntry {
Original = "test0",
Translated = "test1",
Context = "test2",
TranslatorComment = "test3",
ExtractedComments = "test4",
Reference = "test5",
Flags = "test6",
PreviousContext = "test7",
PreviousOriginal = "test8",
};

PoEntry clone = new PoEntry(entry);

Assert.AreNotSame(entry, clone);
Assert.AreEqual("test0", clone.Original);
Assert.AreEqual("test1", clone.Translated);
Assert.AreEqual("test2", clone.Context);
Assert.AreEqual("test3", clone.TranslatorComment);
Assert.AreEqual("test4", clone.ExtractedComments);
Assert.AreEqual("test5", clone.Reference);
Assert.AreEqual("test6", clone.Flags);
Assert.AreEqual("test7", clone.PreviousContext);
Assert.AreEqual("test8", clone.PreviousOriginal);
}
}
}
34 changes: 34 additions & 0 deletions src/Yarhl.UnitTests/Media/Text/PoHeaderTests.cs
Expand Up @@ -74,5 +74,39 @@ public void TestProperties()
Assert.AreEqual("test8", header.PluralForms);
Assert.That(header.Extensions["X-MyExt"], Is.EqualTo("the value"));
}

[Test]
public void CloneNullThrowsException()
{
Assert.Throws<ArgumentNullException>(() => _ = new PoHeader(null));
}

[Test]
public void Clone()
{
var header = new PoHeader("myID", "yo", "us") {
ProjectIdVersion = "test1",
ReportMsgidBugsTo = "test2",
CreationDate = "test3",
RevisionDate = "test4",
LastTranslator = "test5",
LanguageTeam = "test6",
Language = "test7",
PluralForms = "test8",
};
header.Extensions["X-MyExt"] = "the value";

PoHeader clone = new PoHeader(header);
Assert.AreNotSame(header, clone);
Assert.AreEqual("test1", clone.ProjectIdVersion);
Assert.AreEqual("test2", clone.ReportMsgidBugsTo);
Assert.AreEqual("test3", clone.CreationDate);
Assert.AreEqual("test4", clone.RevisionDate);
Assert.AreEqual("test5", clone.LastTranslator);
Assert.AreEqual("test6", clone.LanguageTeam);
Assert.AreEqual("test7", clone.Language);
Assert.AreEqual("test8", clone.PluralForms);
Assert.That(clone.Extensions["X-MyExt"], Is.EqualTo("the value"));
}
}
}
14 changes: 14 additions & 0 deletions src/Yarhl.UnitTests/Media/Text/PoTests.cs
Expand Up @@ -225,6 +225,20 @@ public void FindEntryNullOriginalThrows()
Assert.That(() => po.FindEntry(string.Empty), Throws.ArgumentNullException);
}

[Test]
public void Clone()
{
var header = new PoHeader("id", "reporter", "lang");
var po = new Po(header);
po.Add(new PoEntry("t1"));
po.Add(new PoEntry("t2"));

var clone = (Po)po.DeepClone();

Assert.AreNotSame(po, clone);
Assert.AreEqual(2, clone.Entries.Count);
}

protected override Po CreateDummyFormat()
{
return new Po();
Expand Down
38 changes: 38 additions & 0 deletions src/Yarhl/FileFormat/ICloneableFormat.cs
@@ -0,0 +1,38 @@
// Copyright (c) 2019 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.FileFormat
{
/// <summary>
/// Cloneable format interface.
/// </summary>
public interface ICloneableFormat : IFormat
{
/// <summary>
/// Deep clones a format into a new object.
/// </summary>
/// <returns>The cloned format.</returns>
/// <remarks>
/// <para>A 'deep copy' must create the necessary copies, in the way that
/// any change in the original node (or any of it's member hierarchy) won't affect
/// the cloned node.</para>
/// </remarks>
object DeepClone();
}
}
31 changes: 31 additions & 0 deletions src/Yarhl/FileSystem/Node.cs
Expand Up @@ -20,6 +20,7 @@
namespace Yarhl.FileSystem
{
using System;
using System.Collections.Generic;
using Yarhl.FileFormat;
using Yarhl.IO;

Expand Down Expand Up @@ -48,6 +49,36 @@ public Node(string name, IFormat initialFormat)
ChangeFormat(initialFormat);
}

/// <summary>
/// Initializes a new instance of the <see cref="Node"/> class.
/// </summary>
/// <param name="node">The original node.</param>
/// <remarks><para>It makes a copy of the original node.
/// The original format is deep copied. See <see cref="ICloneableFormat"/> for details.</para>
/// </remarks>
/// <remarks><para>If the node has children, it must be a <see cref="NodeContainerFormat"/> to clone them.
/// In other case, the format must implement <see cref="ICloneableFormat"/> and clone the children explicitly.
/// </para></remarks>
public Node(Node node)
: this(node != null ? node.Name : string.Empty)
{
if (node!.Format != null && !(node.Format is ICloneableFormat))
throw new InvalidOperationException("Format does not implement ICloneableFormat interface.");

ICloneableFormat newFormat = null;
if (node.Format != null) {
var oldFormat = node.Format as ICloneableFormat;
newFormat = (ICloneableFormat)oldFormat!.DeepClone();
}

ChangeFormat(newFormat);

foreach (KeyValuePair<string, dynamic> tag in node.Tags)
{
Tags[tag.Key] = tag.Value;
}
}

/// <summary>
/// Gets the current format of the node.
/// </summary>
Expand Down

0 comments on commit 287b4b9

Please sign in to comment.