diff --git a/sources/Manifest.uc b/sources/Manifest.uc
index e1fead3..4f5e9b7 100644
--- a/sources/Manifest.uc
+++ b/sources/Manifest.uc
@@ -42,18 +42,19 @@ defaultproperties
testCases(11) = class'TEST_JSON'
testCases(12) = class'TEST_TextCache'
testCases(13) = class'TEST_FormattedStrings'
- testCases(14) = class'TEST_User'
- testCases(15) = class'TEST_Memory'
- testCases(16) = class'TEST_DynamicArray'
- testCases(17) = class'TEST_AssociativeArray'
- testCases(18) = class'TEST_CollectionsMixed'
- testCases(19) = class'TEST_Iterator'
- testCases(20) = class'TEST_Command'
- testCases(21) = class'TEST_CommandDataBuilder'
- testCases(22) = class'TEST_LogMessage'
- testCases(23) = class'TEST_DatabaseCommon'
- testCases(24) = class'TEST_LocalDatabase'
- testCases(25) = class'TEST_AcediaConfig'
- testCases(26) = class'TEST_UTF8EncoderDecoder'
- testCases(27) = class'TEST_AvariceStreamReader'
+ testCases(14) = class'TEST_TextTemplate'
+ testCases(15) = class'TEST_User'
+ testCases(16) = class'TEST_Memory'
+ testCases(17) = class'TEST_DynamicArray'
+ testCases(18) = class'TEST_AssociativeArray'
+ testCases(19) = class'TEST_CollectionsMixed'
+ testCases(20) = class'TEST_Iterator'
+ testCases(21) = class'TEST_Command'
+ testCases(22) = class'TEST_CommandDataBuilder'
+ testCases(23) = class'TEST_LogMessage'
+ testCases(24) = class'TEST_DatabaseCommon'
+ testCases(25) = class'TEST_LocalDatabase'
+ testCases(26) = class'TEST_AcediaConfig'
+ testCases(27) = class'TEST_UTF8EncoderDecoder'
+ testCases(28) = class'TEST_AvariceStreamReader'
}
\ No newline at end of file
diff --git a/sources/Text/Tests/TEST_TextTemplate.uc b/sources/Text/Tests/TEST_TextTemplate.uc
new file mode 100644
index 0000000..cd49fd7
--- /dev/null
+++ b/sources/Text/Tests/TEST_TextTemplate.uc
@@ -0,0 +1,361 @@
+/**
+ * Set of tests for functionality of `TextTemplate`.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class TEST_TextTemplate extends TestCase
+ abstract;
+
+protected static function TESTS()
+{
+ Context("Testing `TextTemplate` class' ability to handle empty (erroneous)"
+ @ "argument declarations.");
+ Test_EmptyDeclarations();
+ Context("Testing `TextTemplate` class' ability to handle numeric"
+ @ "argument declarations.");
+ Test_NumericDeclarationsAmount();
+ Test_NumericDeclarationsOrder();
+ Test_NumericDeclarationsNoOrder();
+ Test_NumericDeclarationsRandomValues();
+ Context("Testing `TextTemplate` class' ability to handle text"
+ @ "argument declarations.");
+ Test_TextDeclarationsGet();
+ Test_TextDeclarationsEmptyCollect();
+ Test_TextDeclarationsCollect();
+ Test_TextDeclarationsOverwriteCollect();
+ Context("Testing complex `TextTemplate` scenarios.");
+ Test_Reset();
+ Test_Complex();
+ Test_Formatted();
+}
+
+protected static function Test_EmptyDeclarations()
+{
+ local TextTemplate instance;
+
+ Issue("Empty `TextTemplate` is not collected into empty text.");
+ instance = __().text.MakeTemplate_S("");
+ TEST_ExpectTrue(instance.Collect().IsEmpty());
+
+ Issue("`TextTemplate` with empty numeric definition is not properly"
+ @ "collected.");
+ instance = __().text.MakeTemplate_S("%");
+ TEST_ExpectTrue(instance.Collect().IsEmpty());
+ instance = __().text.MakeTemplate_S("With some%text");
+ TEST_ExpectTrue(instance.Collect().ToString() == "With sometext");
+
+ Issue("`TextTemplate` with empty text definition is not properly"
+ @ "collected.");
+ instance = __().text.MakeTemplate_S("%%");
+ TEST_ExpectTrue(instance.Collect().IsEmpty());
+ instance = __().text.MakeTemplate_S("With some%%%text and %%%%more!");
+ TEST_ExpectTrue(instance.Collect().ToString() == "With sometext and more!");
+
+ Issue("`TextTemplate` with definitions opened at the very end is not"
+ @ "properly collected.");
+ instance = __().text.MakeTemplate_S("Some text: %");
+ TEST_ExpectTrue(instance.Collect().ToString() == "Some text: ");
+ instance = __().text.MakeTemplate_S("Another example: %%");
+ TEST_ExpectTrue(instance.Collect().ToString() == "Another example: ");
+}
+
+protected static function Test_NumericDeclarationsAmount()
+{
+ local TextTemplate instance;
+
+ Issue("`GetNumericArgsAmount()` does not return `0` for `Text` without any"
+ @ "numeric declarations.");
+ instance = __().text.MakeTemplate_S("");
+ TEST_ExpectTrue(instance.GetNumericArgsAmount() == 0);
+ instance = __().text.MakeTemplate_S("With some%text");
+ TEST_ExpectTrue(instance.GetNumericArgsAmount() == 0);
+ instance = __().text.MakeTemplate_S("With some%%%text and %%%%more!");
+ TEST_ExpectTrue(instance.GetNumericArgsAmount() == 0);
+ instance = __().text.MakeTemplate_S("With some%%valid arg%text and"
+ @ "%%me too%%more!");
+ TEST_ExpectTrue(instance.GetNumericArgsAmount() == 0);
+
+ Issue("`GetNumericArgsAmount()` does not correctly counts numeric"
+ @ "declarations.");
+ instance = __().text.MakeTemplate_S("just %1 %2 %3 %t %4 %5 %%text one!%%"
+ @" %6 %7 %8");
+ TEST_ExpectTrue(instance.GetNumericArgsAmount() == 8);
+
+ Issue("`GetNumericArgsAmount()` does not correctly counts numeric"
+ @ "declarations with escaped '%' characters.");
+ instance = __().text.MakeTemplate_S("just &%1 %2 %3 %t &%4 %5 %%text one!%%"
+ @" %6 %7 %8");
+ TEST_ExpectTrue(instance.GetNumericArgsAmount() == 6);
+}
+
+protected static function Test_NumericDeclarationsOrder()
+{
+ local TextTemplate instance;
+
+ Issue("Basic numeric declarations are not working as intended.");
+ instance = __().text.MakeTemplate_S("This is argument: %1!");
+ instance.Arg(P(""));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "This is argument: !");
+ instance = __().text.MakeTemplate_S("just %1 %2 %3 %4 %5 %6 %7 %8");
+ instance.Arg(P("a")).Arg(P("b")).Arg(P("c")).Arg(P("d")).Arg(P("e"))
+ .Arg(P("f")).Arg(P("g")).Arg(P("h"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "just a b c d e f g h");
+
+ Issue("Basic numeric declarations are not working as intended after"
+ @ "specifying too many.");
+ instance = __().text.MakeTemplate_S("This is argument: %1!");
+ instance.Arg(P("")).Arg(P("Now this is too much!"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "This is argument: !");
+ instance = __().text.MakeTemplate_S("just %1 %2 %3 %4 %5 %6 %7 %8");
+ instance.Arg(P("a")).Arg(P("b")).Arg(P("c")).Arg(P("d")).Arg(P("e"))
+ .Arg(P("f")).Arg(P("g")).Arg(P("h")).Arg(P("i")).Arg(P("j"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "just a b c d e f g h");
+
+ Issue("Basic numeric declarations are not working as intended after"
+ @ "specifying too little.");
+ instance = __().text.MakeTemplate_S("This is argument: %1!");
+ TEST_ExpectTrue(instance.Collect().ToString() == "This is argument: !");
+ instance = __().text.MakeTemplate_S("just %1 %2 %3 %4 %5 %6 %7 %8");
+ instance.Arg(P("a")).Arg(P("b")).Arg(P("c")).Arg(P("d")).Arg(P("e"));
+ TEST_ExpectTrue(instance.Collect().ToString() == "just a b c d e ");
+}
+
+protected static function Test_NumericDeclarationsNoOrder()
+{
+ local TextTemplate instance;
+
+ Issue("Basic numeric declarations are not working as intended.");
+ instance = __().text.MakeTemplate_S("This is argument: %2! And this: %1!!");
+ instance.Arg(P("")).Arg(P("heh"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "This is argument: heh! And this: !!");
+ instance = __().text.MakeTemplate_S("just %1 %3 %5 %2 %4 %8 %6 %7");
+ instance.Arg(P("a")).Arg(P("b")).Arg(P("c")).Arg(P("d")).Arg(P("e"))
+ .Arg(P("f")).Arg(P("g")).Arg(P("h"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "just a c e b d h f g");
+
+ Issue("Basic numeric declarations are not working as intended after"
+ @ "specifying too many.");
+ instance = __().text.MakeTemplate_S("just %1 %3 %5 %2 %4 %8 %6 %7");
+ instance.Arg(P("a")).Arg(P("b")).Arg(P("c")).Arg(P("d")).Arg(P("e"))
+ .Arg(P("f")).Arg(P("g")).Arg(P("h")).Arg(P("i")).Arg(P("j"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "just a c e b d h f g");
+
+ Issue("Basic numeric declarations are not working as intended after"
+ @ "specifying too little.");
+ instance = __().text.MakeTemplate_S("just %1 %3 %5 %2 %4 %8 %6 %7");
+ instance.Arg(P("a")).Arg(P("b")).Arg(P("c"));
+ TEST_ExpectTrue(instance.Collect().ToString() == "just a c b ");
+}
+
+protected static function Test_NumericDeclarationsRandomValues()
+{
+ local TextTemplate instance;
+
+ Issue("Basic numeric declarations are not working as intended when using"
+ @ "arbitrary integer values.");
+ instance = __().text.MakeTemplate_S("This is argument:"
+ @ "%205! And this: %-7!!");
+ instance.Arg(P("")).Arg(P("heh"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "This is argument: heh! And this: !!");
+ instance = __().text.MakeTemplate_S("just %-3912842 %0 %666 %-111 %34"
+ @ "%666999 %10234 %10235");
+ instance.Arg(P("a")).Arg(P("b")).Arg(P("c")).Arg(P("d")).Arg(P("e"))
+ .Arg(P("f")).Arg(P("g")).Arg(P("h"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "just a c e b d h f g");
+}
+
+protected static function Test_TextDeclarationsGet()
+{
+ local TextTemplate instance;
+ local array result;
+
+ Issue("Specified text labels aren't properly returned by"
+ @ "the `GetTextlabels()` method.");
+ instance = __().text.MakeTemplate_S("This is argument: %%argument!%% and"
+ @ "so its this: %%another arg%%");
+ result = instance.GetTextArgs();
+ TEST_ExpectTrue(result[0].ToString() == "argument!");
+ TEST_ExpectTrue(result[1].ToString() == "another arg");
+ TEST_ExpectTrue(result.length == 2);
+ instance.TextArg(P("another arg"), P("heh"))
+ .TextArg(P("argument!"), P(""));
+ result = instance.GetTextArgs();
+ TEST_ExpectTrue(result[0].ToString() == "argument!");
+ TEST_ExpectTrue(result[1].ToString() == "another arg");
+ TEST_ExpectTrue(result.length == 2);
+
+ instance = __().text.MakeTemplate_S("Here: %2 %76 %4 %5 %8");
+ result = instance.GetTextArgs();
+ TEST_ExpectTrue(result.length == 0);
+
+ instance = __().text.MakeTemplate_S("More %%complex%% %7 %%/example% here,"
+ @ "yes. Very %%complex%!");
+ result = instance.GetTextArgs();
+ TEST_ExpectTrue(result[0].ToString() == "complex");
+ TEST_ExpectTrue(result[1].ToString() == "/example");
+ TEST_ExpectTrue(result.length == 2);
+}
+
+protected static function Test_TextDeclarationsEmptyCollect()
+{
+ local TextTemplate instance;
+
+ Issue("`TextTempalte` is not properly collected with it contains empty"
+@ "text labels.");
+ instance = __().text.MakeTemplate_S("This is argument: %%%% and"
+ @ "so its this: %%%");
+ instance.TextArg(P(""), P("what do y'll know?!"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == ("This is argument: what do y'll know?! and so its this:"
+ @ "what do y'll know?!"));
+}
+
+protected static function Test_TextDeclarationsCollect()
+{
+ local TextTemplate instance;
+
+ Issue("Specified text labels aren't properly collected.");
+ instance = __().text.MakeTemplate_S("This is argument: %%argument!%% and"
+ @ "so its this: %%another arg%%");
+ instance.TextArg(P("another arg"), P("heh"))
+ .TextArg(P("argument!"), P(""));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "This is argument: and so its this: heh");
+
+ Issue("Specified text labels aren't properly collected when duplicate"
+ @ "labels are present.");
+ instance = __().text.MakeTemplate_S("More %%complex%% %7 %%/example% here,"
+ @ "yes. Very %%complex%!");
+ instance.TextArg(P("complex"), P("simple")).TextArg(P("/example"), P("!!"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "More simple !! here, yes. Very simple!");
+
+ Issue("Specified text labels aren't properly collected when specified"
+ @ "too little arguments.");
+ instance = __().text.MakeTemplate_S("More %%complex%% %7 %%/example% here,"
+ @ "yes. Very %%complex% and %%nasty%%!!!");
+ instance.TextArg(P("complex"), P("simple")).TextArg(P("nasty"), P("-_-"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "More simple here, yes. Very simple and -_-!!!");
+
+ Issue("Specified text labels aren't properly collected when specified"
+ @ "too many arguments.");
+ instance = __().text.MakeTemplate_S("More %%complex%% %11 %%/example% here,"
+ @ "yes. Very %%complex% and %%nasty%%!!!");
+ instance.TextArg(P("complex"), P("simple")).TextArg(P("nasty"), P("-_-"))
+ .TextArg(P("/usr/bin"), P("geronimo")).TextArg(P("/example"), P("???"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "More simple ??? here, yes. Very simple and -_-!!!");
+}
+
+protected static function Test_TextDeclarationsOverwriteCollect()
+{
+ local TextTemplate instance;
+
+ Issue("Specified text labels aren't properly collected when some labels"
+ @ "are overwritten.");
+ instance = __().text.MakeTemplate_S("More %%complex%% %7 %%/example% here,"
+ @ "yes. Very %%complex%!");
+ instance.TextArg(P("complex"), P("simple")).TextArg(P("/example"), P("!!"))
+ .TextArg(P("complex"), P("nasty")).TextArg(P("/exe"), P("???"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == "More nasty !! here, yes. Very nasty!");
+}
+
+protected static function Test_Reset()
+{
+ local TextTemplate instance;
+
+ Issue("`Reset()` does not properly reset user's input.");
+ instance = __().text.MakeTemplate_S("Testing %1, %2, %3 and %%one%%,"
+ @ "%%two%% + %%three%%");
+ instance.Arg_S("1").Arg_S("2").Arg_S("3").Arg_S("more?");
+ instance
+ .TextArg(P("one"), P("4"))
+ .TextArg(P("two"), P("5"))
+ .TextArg(P("three"), P("6"));
+ instance.Reset().Arg_S("HEY").Arg_S("ARE").Arg_S("YOU");
+ instance
+ .TextArg(P("one"), P("READY"))
+ .TextArg(P("two"), P("TO"))
+ .TextArg(P("three"), P("GO?"));
+ TEST_ExpectTrue(instance.Collect().ToString() == ("Testing HEY, ARE, YOU"
+ @ "and READY, TO + GO?"));
+}
+
+protected static function Test_Complex()
+{
+ local TextTemplate instance;
+ local array result;
+
+ Issue("Specified text labels aren't properly collected in complex scenario"
+ @ "with several numeric / text arguments and escaped characters.");
+ instance = __().text.MakeTemplate_S("Welcome %%MoonAndStar%%, it is %7 nice"
+ @ "to %-2 %%you%%. %%MoonAndStar%% drop your %%weapons%%, it is not too"
+ @ "%0 for &m&y %%mercy%&%!");
+ instance
+ .TextArg(P("MoonAndStar"), P("Nerevar"))
+ .TextArg(P("you"), P("you"))
+ .TextArg(P("weapons"), P("cheats"))
+ .Arg(P("see"))
+ .Arg(P("late"))
+ .Arg(P("so"));
+ TEST_ExpectTrue(instance.Collect().ToString()
+ == ("Welcome Nerevar, it is so nice to see you."
+ @ "Nerevar drop your cheats, it is not too late for &m&y %!"));
+ result = instance.GetTextArgs();
+ TEST_ExpectTrue(result[0].ToString() == "MoonAndStar");
+ TEST_ExpectTrue(result[1].ToString() == "you");
+ TEST_ExpectTrue(result[2].ToString() == "weapons");
+ TEST_ExpectTrue(result[3].ToString() == "mercy");
+ TEST_ExpectTrue(result.length == 4);
+}
+
+protected static function Test_Formatted()
+{
+ local TextTemplate instance;
+
+ Issue("Specified text labels aren't properly collected in complex scenario"
+ @ "with several numeric / text arguments and escaped characters and"
+ @ "we are asking to parse template as a formatted string"
+ @ "(`CollectFormatted()` method).");
+ instance = __().text.MakeTemplate_S("Test simple {%%color%% %1} string"
+ @ "that is {%2 %%what%%}!");
+ instance
+ .Arg(P("formatted"))
+ .Arg(P("$blue"))
+ .TextArg(P("color"), P("$red"))
+ .TextArg(P("what"), P("colored"));
+ TEST_ExpectTrue(instance.CollectFormatted().ToFormattedString()
+ == ("Test simple {rgb(255,0,0) formatted} string that is"
+ @ "{rgb(0,0,255) colored}!"));
+}
+
+defaultproperties
+{
+ caseName = "TextTemplate"
+ caseGroup = "Text"
+}
\ No newline at end of file
diff --git a/sources/Text/TextAPI.uc b/sources/Text/TextAPI.uc
index 9c87c53..ae2757d 100644
--- a/sources/Text/TextAPI.uc
+++ b/sources/Text/TextAPI.uc
@@ -667,6 +667,41 @@ public final function array PartsS(string source)
return result;
}
+/**
+ * Creates and initializes with `source` new `TextTemplate` instance.
+ *
+ * @param source Data to initialize new `TextTemplate` with.
+ * @return `TextTemplate` based on `source`.
+ * `none` if given `source` is `none`.
+ */
+public final function TextTemplate MakeTemplate(BaseText source)
+{
+ local TextTemplate newTemplate;
+ if (source == none) {
+ return none;
+ }
+ newTemplate = TextTemplate(_.memory.Allocate(class'TextTemplate'));
+ newTemplate.Initialize(source);
+ return NewTemplate;
+}
+
+/**
+ * Creates and initializes with `source` new `TextTemplate` instance.
+ *
+ * @param source Data to initialize new `TextTemplate` with.
+ * @return `TextTemplate` based on `source`.
+ * `none` if given `source` is `none`.
+ */
+public final function TextTemplate MakeTemplate_S(string source)
+{
+ local MutableText wrapper;
+ local TextTemplate result;
+ wrapper = FromStringM(source);
+ result = MakeTemplate(wrapper);
+ _.memory.Free(wrapper);
+ return result;
+}
+
/**
* Creates a new, empty `MutableText`.
*
@@ -751,12 +786,56 @@ public final function MutableText FromColoredStringM(string source)
}
/**
- * Creates a `Text` that will contain a given formatted `string`.
+ * Creates a `Text` that will contain a parsed formatted string inside
+ * the given `BaseText`.
+ *
+ * To create `MutableText` instead use `FromFormattedM()` method.
+ *
+ * @param source Formatted string inside `Text` that will be parsed into
+ * returned `Text`.
+ * @return New instance of `Text` that will contain parsed formatted `source`.
+ */
+public final function Text FromFormatted(BaseText source)
+{
+ local MutableText builder;
+ local Text result;
+ if (source == none) {
+ return none;
+ }
+ builder = MutableText(_.memory.Allocate(class'MutableText'));
+ result = builder.AppendFormatted(source).Copy();
+ builder.FreeSelf();
+ return result;
+}
+
+/**
+ * Creates a `MutableText` that will contain a parsed formatted string inside
+ * the given `BaseText`.
+ *
+ * To create `Text` instead use `FromFormatted()` method.
+ *
+ * @param source Formatted string inside `Text` that will be parsed into
+ * returned `Text`.
+ * @return New instance of `MutableText` that will contain parser formatted
+ * `source`.
+ */
+public final function MutableText FromFormattedM(BaseText source)
+{
+ local MutableText newText;
+ if (source == none) {
+ return none;
+ }
+ newText = MutableText(_.memory.Allocate(class'MutableText'));
+ return newText.AppendFormatted(source);
+}
+
+/**
+ * Creates a `Text` that will contain parsed formatted `string`.
*
* To create `MutableText` instead use `FromFormattedStringM()` method.
*
* @param source Formatted `string` that will be copied into returned `Text`.
- * @return New instance of `Text` that will contain passed formatted `string`.
+ * @return New instance of `Text` that will contain parsed formatted `string`.
*/
public final function Text FromFormattedString(string source)
{
@@ -769,13 +848,13 @@ public final function Text FromFormattedString(string source)
}
/**
- * Creates a `MutableText` that will contain a given formatted `string`.
+ * Creates a `MutableText` that will contain parsed formatted `string`.
*
* To create immutable `Text` instead use `FromFormattedString()` method.
*
* @param source Formatted `string` that will be copied into
* returned `MutableText`.
- * @return New instance of `MutableText` that will contain passed
+ * @return New instance of `MutableText` that will contain parsed
* formatted `string`.
*/
public final function MutableText FromFormattedStringM(string source)
diff --git a/sources/Text/TextTemplate.uc b/sources/Text/TextTemplate.uc
new file mode 100644
index 0000000..c1aa044
--- /dev/null
+++ b/sources/Text/TextTemplate.uc
@@ -0,0 +1,975 @@
+/**
+ * Class that allows to work with simple text templates in the form of
+ * "Template example, following can be replaced: %1, %2, %3 or %4" or
+ * "%%instigator%% {%%rage_color%% raged} %%target_zed%%!".
+ * Part noted by '%' characters can be replaced with arbitrary values.
+ * It should be more efficient that simply repeatedly calling
+ * `MutableText.Replace()` method.
+ * Copyright 2022 Anton Tarasenko
+ *------------------------------------------------------------------------------
+ * This file is part of Acedia.
+ *
+ * Acedia is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * Acedia is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with Acedia. If not, see .
+ */
+class TextTemplate extends AcediaObject;
+
+/**
+ * # `TextTemplate`
+ *
+ * This class allows to do more efficient replacements in the templates of
+ * form "Template example, following can be replaced: %1, %2, %3 or %4" or
+ * "%%instigator%% {%%rage_color%% raged} %%target_zed%%!" that simple
+ * repeating of `MutableText.Replace()` method would allow.
+ *
+ * ## Usage
+ *
+ * All `TextTemplate`s must be initialized before they can be used.
+ * `_.text.MakeTemplate()` method is considered a recommended way to create
+ * a new `TextTemplate` and will initialize it for you. In case you have
+ * manually allocated `TextTemplate` you need to simply call
+ * `TextTemplate.Initialize()` method and provide is with a template source
+ * with designated replaceable part (called *labels*). Any label is designated
+ * by either:
+ *
+ * * Specifying a number after a single '%' character (numeric labels);
+ * * Or specifying a textual value in between double percent sequence "%%".
+ *
+ * Numeric labels can then be replaced with user values by various provided
+ * `Arg...()` methods and text labels by `TextArg...()` methods. To reuse
+ * template with another values replacing specified labels, simply call
+ * `TextTemplate.Reset()` method and fill labels anew.
+ *
+ * ### Escaped sequences
+ *
+ * `TextTemplate` allows for the escaped sequences in the style of
+ * formatted strings: the ones based on the '&' character. More precisely, if
+ * you wish to enter actual '%' character in the template, you can escape it
+ * with "&%" and it will be treaded as simply '%' character and not a part of
+ * label definition.
+ * NOTE: any other escaped sequences will be ignored and translated
+ * one-to-one. For example, "{%%color%% &{...&}&%!}", if specified that
+ * "color" -> "$red", will be translated into "{$red &{...&}%!}". This allows
+ * to use `TextTemplate`s to prepare formatted strings in a more convenient
+ * fashion.
+ *
+ * ### Error handling
+ *
+ * `TextTemplate` lack any means of reporting errors in template's formatting
+ * and tries to correct as many errors as possible.
+ *
+ * 1. If after the single '%' character follows not a number, but another
+ * character - that '%' will be ignored and thrown away;
+ * 2. After opening text label with double percent "%%" to stop specifying
+ * the text label it is sufficient to type a single '%' character.
+ *
+ * ### Numeric labels values
+ *
+ * You can specify any values for numeric labels and they will be filled in
+ * order. However the proper notation is to start with `1` and then use
+ * following natural numbers in order (e.g. "%1", "%2", "%3", etc.).
+ *
+ * ### Text labels duplicates
+ *
+ * It is allowed for the same text label to appear several times in a template
+ * and each entry will be replaced with a specified user value.
+ *
+ * ### Unspecified arguments
+ *
+ * If no argument was given for some label - it will simply be replaced with
+ * empty text.
+ */
+
+// *Labels* will be an internal name for the parts of the template that is
+// supposed to be replaced. This struct describes one such part.
+struct Label
+{
+ // If Label starts with two percent characters ("%%"), then it is
+ // a text Label and we will remember contents within "%%...%%" in this
+ // field (`textLabel != none`).
+ // Otherwise it is a numeric Label and `textLabel` will be set
+ // to `none`.
+ var MutableText textLabel;
+ // For numeric labels - number after that percent character '%'.
+ // In case numeric labels were specified incorrectly, these values will
+ // be normalized to start from `1` and increase by +1, while preserving
+ // their order.
+ var int numberLabel;
+ // Before which part (from `parts`) to insert this Label?
+ var int insertionIndex;
+};
+
+var private bool initialized;
+// Remembers amount of numeric labels in the template
+var private int numericLabelsAmount;
+// Arrays of labels and parts in between that template got broken into
+// during initialization
+var private array