From 21e50b2a00b3aea83985cb4d4bdf64db1ad74a79 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 30 Oct 2020 23:42:11 +0000 Subject: [PATCH 1/6] Fixes #51. Value.Tuple is not needed and added more Rune unit tests. --- NStack/NStack.csproj | 6 - NStackTests/NStackTests.csproj | 3 - NStackTests/RuneTest.cs | 193 +++++++++++++++++++++++++++++++-- NStackTests/packages.config | 1 - 4 files changed, 181 insertions(+), 22 deletions(-) diff --git a/NStack/NStack.csproj b/NStack/NStack.csproj index fedfdc1..f4f94ac 100644 --- a/NStack/NStack.csproj +++ b/NStack/NStack.csproj @@ -46,11 +46,5 @@ Added ustring.ColumnWidth to return number of columns that a ustring takes in a true Latest - - - ..\packages\System.ValueTuple.4.3.1\lib\netstandard1.0\System.ValueTuple.dll - False - - diff --git a/NStackTests/NStackTests.csproj b/NStackTests/NStackTests.csproj index 9a3efaa..4a1b904 100644 --- a/NStackTests/NStackTests.csproj +++ b/NStackTests/NStackTests.csproj @@ -35,9 +35,6 @@ ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll - - ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll - diff --git a/NStackTests/RuneTest.cs b/NStackTests/RuneTest.cs index 1c83185..80aa74e 100644 --- a/NStackTests/RuneTest.cs +++ b/NStackTests/RuneTest.cs @@ -1,7 +1,12 @@ -๏ปฟusing NUnit.Framework; +๏ปฟusing NStack; +using NUnit.Framework; using System; -namespace NStackTests { - public class RuneTest { +using System.Globalization; + +namespace NStackTests +{ + public class RuneTest + { Rune a = 'a'; Rune b = 'b'; Rune c = 123; @@ -13,38 +18,202 @@ public class RuneTest { [Test] public void TestColumnWidth() { - var rt = new RuneTest(); - - Assert.AreEqual(1, Rune.ColumnWidth(rt.a)); - Assert.AreEqual(1, Rune.ColumnWidth(rt.b)); + Assert.AreEqual(1, Rune.ColumnWidth(a)); + Assert.AreEqual("a", a.ToString()); + Assert.AreEqual(1, Rune.ColumnWidth(b)); + Assert.AreEqual("b", b.ToString()); var l = a < b; Assert.IsTrue(l); - Assert.AreEqual(1, Rune.ColumnWidth(rt.c)); - Assert.AreEqual(2, Rune.ColumnWidth(rt.d)); - Assert.AreEqual(0, Rune.ColumnWidth(rt.e)); - Assert.AreEqual(-1, Rune.ColumnWidth(rt.f)); - Assert.AreEqual(-1, Rune.ColumnWidth(rt.g)); + Assert.AreEqual(1, Rune.ColumnWidth(c)); + Assert.AreEqual("{", c.ToString()); + Assert.AreEqual(2, Rune.ColumnWidth(d)); + Assert.AreEqual("แ…", d.ToString()); + Assert.AreEqual(0, Rune.ColumnWidth(e)); + Assert.AreEqual("แ…ก", e.ToString()); + Assert.AreEqual(-1, Rune.ColumnWidth(f)); + Assert.AreEqual(1, f.ToString().Length); + Assert.AreEqual(-1, Rune.ColumnWidth(g)); + Assert.AreEqual(1, g.ToString().Length); } [Test] public void TestRune() { Rune a = new Rune('a'); + Assert.AreEqual(1, Rune.ColumnWidth(a)); + Assert.AreEqual(1, a.ToString().Length); Assert.AreEqual("a", a.ToString()); Rune b = new Rune(0x0061); + Assert.AreEqual(1, Rune.ColumnWidth(b)); + Assert.AreEqual(1, b.ToString().Length); Assert.AreEqual("a", b.ToString()); Rune c = new Rune('\u0061'); + Assert.AreEqual(1, Rune.ColumnWidth(c)); + Assert.AreEqual(1, c.ToString().Length); Assert.AreEqual("a", c.ToString()); Rune d = new Rune(0x10421); + Assert.AreEqual(1, Rune.ColumnWidth(d)); + Assert.AreEqual(2, d.ToString().Length); Assert.AreEqual("๐ก", d.ToString()); Assert.Throws(() => new Rune('\ud799', '\udc21')); Rune e = new Rune('\ud801', '\udc21'); + Assert.AreEqual(1, Rune.ColumnWidth(e)); + Assert.AreEqual(2, e.ToString().Length); Assert.AreEqual("๐ก", e.ToString()); Assert.Throws(() => new Rune('\ud801')); Rune f = new Rune('\ud83c', '\udf39'); + Assert.AreEqual(1, Rune.ColumnWidth(f)); + Assert.AreEqual(2, f.ToString().Length); Assert.AreEqual("๐ŸŒน", f.ToString()); Assert.DoesNotThrow(() => new Rune(0x10ffff)); + var g = new Rune(0x10ffff); + Assert.AreEqual(1, Rune.ColumnWidth(g)); + Assert.AreEqual(2, g.ToString().Length); + Assert.AreEqual("๔ฟฟ", g.ToString()); Assert.Throws(() => new Rune(0x12345678)); + var h = new Rune('\u1150'); + Assert.AreEqual(2, Rune.ColumnWidth(h)); + Assert.AreEqual(1, h.ToString().Length); + Assert.AreEqual("แ…", h.ToString()); + var i = new Rune('\u4F60'); + Assert.AreEqual(2, Rune.ColumnWidth(i)); + Assert.AreEqual(1, i.ToString().Length); + Assert.AreEqual("ไฝ ", i.ToString()); + var j = new Rune('\u597D'); + Assert.AreEqual(2, Rune.ColumnWidth(j)); + Assert.AreEqual(1, j.ToString().Length); + Assert.AreEqual("ๅฅฝ", j.ToString()); + var k = new Rune('\ud83d', '\udc02'); + Assert.AreEqual(1, Rune.ColumnWidth(k)); + Assert.AreEqual(2, k.ToString().Length); + Assert.AreEqual("๐Ÿ‚", k.ToString()); + var l = new Rune('\ud801', '\udcbb'); + Assert.AreEqual(1, Rune.ColumnWidth(l)); + Assert.AreEqual(2, l.ToString().Length); + Assert.AreEqual("๐’ป", l.ToString()); + var m = new Rune('\ud801', '\udccf'); + Assert.AreEqual(1, Rune.ColumnWidth(m)); + Assert.AreEqual(2, m.ToString().Length); + Assert.AreEqual("๐“", m.ToString()); + var n = new Rune('\u00e1'); + Assert.AreEqual(1, Rune.ColumnWidth(n)); + Assert.AreEqual(1, n.ToString().Length); + Assert.AreEqual("รก", n.ToString()); + var o = new Rune('\ud83d', '\udd2e'); + Assert.AreEqual(1, Rune.ColumnWidth(o)); + Assert.AreEqual(2, o.ToString().Length); + Assert.AreEqual("๐Ÿ”ฎ", o.ToString()); + + PrintTextElementCount(ustring.Make('\u00e1'), "รก", 1, 1, 1, 1); + PrintTextElementCount(ustring.Make('\u0061', '\u0301'), "aฬ", 1, 2, 2, 1); + PrintTextElementCount(ustring.Make(new Rune[] { new Rune(0x1f469), new Rune(0x1f3fd), new Rune('\u200d'), new Rune(0x1f692) }), + "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš’", 3, 4, 7, 4); + PrintTextElementCount(ustring.Make(new Rune[] { new Rune(0x1f469), new Rune(0x1f3fd), new Rune('\u200d'), new Rune(0x1f692) }), + "\U0001f469\U0001f3fd\u200d\U0001f692", 3, 4, 7, 4); + PrintTextElementCount(ustring.Make(new Rune('\ud801', '\udccf')), + "\ud801\udccf", 1, 1, 2, 1); + } + + void PrintTextElementCount(ustring us, string s, int consoleWidth, int runeCount, int stringCount, int txtElementCount) + { + Assert.AreEqual(us.ToString(), s); + Assert.AreEqual(s, us.ToString()); + Assert.AreEqual(consoleWidth, us.ConsoleWidth); + Assert.AreEqual(runeCount, us.RuneCount); + Assert.AreEqual(stringCount, s.Length); + + TextElementEnumerator enumerator = StringInfo.GetTextElementEnumerator(s); + + int textElementCount = 0; + while (enumerator.MoveNext()) + { + textElementCount++; // For versions prior to Net5.0 the StringInfo class might handle some grapheme clusters incorrectly. + } + + Assert.AreEqual(txtElementCount, textElementCount); + } + + [Test] + public void TestRuneIsLetter() + { + Assert.AreEqual(5, CountLettersInString("Hello")); + Assert.AreEqual(8, CountLettersInString("๐“๐“˜๐“ป๐“˜๐“ป๐“Ÿ ๐’ป๐“Ÿ")); + } + + int CountLettersInString(string s) + { + int letterCount = 0; + var us = ustring.Make(s); + + foreach (Rune rune in us.ToRunes()) + { + if (Rune.IsLetter(rune)) + { letterCount++; } + } + + return letterCount; + } + + [Test] + public void TestSurrogatePair() + { + Assert.IsTrue(ProcessTestStringUseChar("๐“๐“˜๐“ป๐“˜๐“ป๐“Ÿ ๐’ป๐“Ÿ")); + Assert.Throws(() => ProcessTestStringUseChar("\ud801")); + + Assert.IsTrue(ProcessStringUseRune("๐“๐“˜๐“ป๐“˜๐“ป๐“Ÿ ๐’ป๐“Ÿ")); + Assert.Throws(() => ProcessStringUseRune("\ud801")); + } + + bool ProcessTestStringUseChar(string s) + { + for (int i = 0; i < s.Length; i++) + { + if (!char.IsSurrogate(s[i])) + { + var buff = new byte[4]; + Rune.EncodeRune(s[i], buff); + Assert.AreEqual((int)(s[i]), buff[0]); + } + else if (i + 1 < s.Length && char.IsSurrogatePair(s[i], s[i + 1])) + { + int codePoint = char.ConvertToUtf32(s[i], s[i + 1]); + Assert.AreEqual(codePoint, Rune.DecodeSurrogatePair(s[i], s[i + 1])); + i++; // so that when the loop iterates it's actually +2 + } + else + { + throw new Exception("String was not well-formed UTF-16."); + } + } + return true; + } + + bool ProcessStringUseRune(string s) + { + var us = ustring.Make(s); + int i = 0; + + foreach (Rune rune in us.ToRunes()) + { + if (rune == Rune.Error) + { + throw new Exception("String was not well-formed UTF-16."); + } + Assert.IsTrue(Rune.ValidRune(rune)); + i += Rune.ColumnWidth(rune); // increment the iterator by the number of chars in this Rune + } + Assert.AreEqual(us.ConsoleWidth, i); + return true; + } + + [Test] + public void TestSplit() + { + string inputString = "๐Ÿ‚, ๐Ÿ„, ๐Ÿ†"; + string[] splitOnSpace = inputString.Split(' '); + string[] splitOnComma = inputString.Split(','); + Assert.AreEqual(3, splitOnSpace.Length); + Assert.AreEqual(3, splitOnComma.Length); } } } diff --git a/NStackTests/packages.config b/NStackTests/packages.config index 2c21c55..20d68e1 100644 --- a/NStackTests/packages.config +++ b/NStackTests/packages.config @@ -1,5 +1,4 @@ ๏ปฟ - \ No newline at end of file From 97091af4e9f7de29845297cccd5db38900b9e915 Mon Sep 17 00:00:00 2001 From: BDisp Date: Fri, 6 Nov 2020 16:02:19 +0000 Subject: [PATCH 2/6] Some more improvements, more unit tests and typo fixing. --- NStack/strings/ustring.cs | 19 +++-- NStack/unicode/Graphic.cs | 8 +- NStack/unicode/Rune.cs | 53 +++++++----- NStack/unicode/Rune.extensions.cs | 8 +- NStackTests/RuneTest.cs | 130 +++++++++++++++++++++++++++++- 5 files changed, 179 insertions(+), 39 deletions(-) diff --git a/NStack/strings/ustring.cs b/NStack/strings/ustring.cs index e41c064..2b8edc8 100644 --- a/NStack/strings/ustring.cs +++ b/NStack/strings/ustring.cs @@ -481,6 +481,7 @@ public static ustring Make (IEnumerable runes) return Make (runes.ToList ()); } + /// /// Initializes a new instance of the class from an array of uints, which contain CodePoints. /// /// The make. @@ -779,7 +780,7 @@ public bool Equals (ustring other) /// /// Reports whether this string and the provided string, when interpreted as UTF-8 strings, are equal under Unicode case-folding /// - /// true, if fold was equalsed, false otherwise. + /// true, if fold was equaled, false otherwise. /// Other. public bool EqualsFold (ustring other) { @@ -888,7 +889,7 @@ public static ustring Make (byte [] buffer, int start, int count) /// Returns the byte at the specified position. /// /// The byte encoded at the specified position. - /// The index value shoudl be between 0 and Length-1. + /// The index value should be between 0 and Length-1. public abstract byte this [int index] { get; } /// @@ -903,7 +904,7 @@ public static ustring Make (byte [] buffer, int start, int count) /// Returns a slice of the ustring delimited by the [start, end) range. If the range is invalid, the return is the Empty string. /// /// Start index, this value is inclusive. If the value is negative, the value is added to the length, allowing this parameter to count to count from the end of the string. - /// End index, this value is exclusive. If the value is negative, the value is added to the length, plus one, allowing this parameter to count from the end of the string. + /// End index, this value is exclusive. If the value is negative, the value is added to the length, plus one, allowing this parameter to count from the end of the string. /// /// /// Some examples given the string "1234567890": @@ -1011,7 +1012,7 @@ public static ustring Make (byte [] buffer, int start, int count) /// Utf8 encoded string. /// /// The substring starting at the specified offset. - /// Starting point, the value is . + /// Starting point, the value is . public ustring Substring (int byteStart) { int len = Length; @@ -1166,6 +1167,7 @@ public List ToRuneList () return result; } + /// /// Converts a ustring into a rune array. /// /// An array containing the runes for the string up to the specified limit. @@ -1541,7 +1543,7 @@ public int IndexOfAny (params uint [] runes) } /// - /// Reports the zero-based index position of the last occurrence in this instance of one or more characters specified in the uustring. + /// Reports the zero-based index position of the last occurrence in this instance of one or more characters specified in the ustring. /// /// The index position of the last occurrence in this instance where any character in was found; -1 if no character in was found. /// The string containing characters to seek. @@ -2170,7 +2172,7 @@ public ustring Replace (ustring oldValue, ustring newValue, int maxReplacements var oldLen = oldValue.Length; var newLen = newValue.Length; - // Apply replcements to buffer + // Apply replacements to buffer var result = new byte [Length + maxReplacements * (newValue.Length - oldValue.Length)]; int w = 0, start = 0; for (int i = 0; i < maxReplacements; i++) { @@ -2196,6 +2198,11 @@ public ustring Replace (ustring oldValue, ustring newValue, int maxReplacements return new ByteBufferUString (result); } + /// + /// Represent the null or empty value related to the ustring. + /// + /// + /// public static bool IsNullOrEmpty (ustring value) { if (value == null) diff --git a/NStack/unicode/Graphic.cs b/NStack/unicode/Graphic.cs index cb6cb07..36f6cc9 100644 --- a/NStack/unicode/Graphic.cs +++ b/NStack/unicode/Graphic.cs @@ -33,7 +33,7 @@ internal enum CharClass : byte { /// /// Determines if a rune is on a set of ranges. /// - /// true, if rune in ranges was ised, false otherwise. + /// true, if rune in ranges was used, false otherwise. /// Rune. /// In ranges. public static bool IsRuneInRanges (uint rune, params RangeTable [] inRanges) @@ -47,7 +47,7 @@ public static bool IsRuneInRanges (uint rune, params RangeTable [] inRanges) /// /// IsGraphic reports whether the rune is defined as a Graphic by Unicode. /// - /// true, if the rune is a lower case lette, false otherwise. + /// true, if the rune is a lower case letter, false otherwise. /// The rune to test for. /// /// Such characters include letters, marks, numbers, punctuation, symbols, and @@ -63,7 +63,7 @@ public static bool IsGraphic (uint rune) /// /// IsPrint reports whether the rune is defined as printable. /// - /// true, if the rune is a lower case lette, false otherwise. + /// true, if the rune is a lower case letter, false otherwise. /// The rune to test for. /// /// Such characters include letters, marks, numbers, punctuation, symbols, and the @@ -81,7 +81,7 @@ public static bool IsPrint (uint rune) /// /// IsControl reports whether the rune is a control character. /// - /// true, if the rune is a lower case lette, false otherwise. + /// true, if the rune is a lower case letter, false otherwise. /// The rune to test for. /// /// The C (Other) Unicode category includes more code points such as surrogates; use C.InRange (r) to test for them. diff --git a/NStack/unicode/Rune.cs b/NStack/unicode/Rune.cs index e06a535..19f395e 100644 --- a/NStack/unicode/Rune.cs +++ b/NStack/unicode/Rune.cs @@ -31,7 +31,7 @@ public partial struct Rune { /// /// Represents invalid code points. /// - public static Rune ReplacementChar = new Rune (0xfffd); + public static Rune ReplacementChar = new Rune (0xfffd); /// /// Maximum number of bytes required to encode every unicode code point. @@ -44,7 +44,7 @@ public partial struct Rune { /// Unsigned integer. /// /// The value does not have to be a valid Unicode code point, this API - /// will create an instanceof Rune regardless of the whether it is in + /// will create an instance of Rune regardless of the whether it is in /// range or not. /// public Rune (uint rune) @@ -76,21 +76,32 @@ public Rune (char ch) /// The low surrogate code points maximum value. public Rune (uint sgateMin, uint sgateMax) { - if (sgateMin < surrogateMin || sgateMax > surrogateMax) + var rune = DecodeSurrogatePair (sgateMin, sgateMax); + if (rune > 0) + { + this.value = rune; + } + else { throw new ArgumentOutOfRangeException($"Must be between {surrogateMin:x} and {surrogateMax:x} inclusive!"); } - this.value = DecodeSurrogatePair(sgateMin, sgateMax); } /// - /// Gets a value indicating whether this can be encoded as UTF-8 from a surrogate pair. + /// Gets a value indicating whether this can be encoded as UTF-8 from a surrogate pair or zero otherwise. /// /// The high surrogate code points minimum value. /// The low surrogate code points maximum value. - public static uint DecodeSurrogatePair(uint sgateMin, uint sgateMax) + public static uint DecodeSurrogatePair (uint sgateMin, uint sgateMax) { - return 0x10000 + ((sgateMin - surrogateMin) * 0x0400) + (sgateMax - lowSurrogateMin); + if (sgateMin < surrogateMin || sgateMax > surrogateMax) + { + return 0; + } + else + { + return 0x10000 + ((sgateMin - surrogateMin) * 0x0400) + (sgateMax - lowSurrogateMin); + } } /// @@ -229,7 +240,7 @@ public static bool FullRune (byte [] p) /// /// Byte buffer containing the utf8 string. /// Starting offset to look into.. - /// Number of bytes valid in the buffer, or -1 to make it the lenght of the buffer. + /// Number of bytes valid in the buffer, or -1 to make it the length of the buffer. public static (Rune rune, int Size) DecodeRune (byte [] buffer, int start = 0, int n = -1) { if (buffer == null) @@ -294,7 +305,7 @@ public static (Rune rune, int Size) DecodeRune (byte [] buffer, int start = 0, i /// it returns (RuneError, 0). Otherwise, if /// the encoding is invalid, it returns (RuneError, 1). Both are impossible /// results for correct, non-empty UTF-8. - /// Scan up to that point, if the value is -1, it sets the value to the lenght of the buffer. + /// Scan up to that point, if the value is -1, it sets the value to the length of the buffer. /// /// An encoding is invalid if it is incorrect UTF-8, encodes a rune that is /// out of range, or is not the shortest possible UTF-8 encoding for the @@ -402,7 +413,7 @@ public static int EncodeRune (Rune rune, byte [] dest, int offset = 0) /// /// Returns the number of runes in a utf8 encoded buffer /// - /// Numnber of runes. + /// Number of runes. /// Byte buffer containing a utf8 string. /// Starting offset in the buffer. /// Number of bytes to process in buffer, or -1 to process until the end of the buffer. @@ -476,7 +487,7 @@ public static bool Valid (byte [] buffer) /// /// Use to find the index of the first invalid utf8 byte sequence in a buffer /// - /// The index of the first insvalid byte sequence or -1 if the entire buffer is valid. + /// The index of the first invalid byte sequence or -1 if the entire buffer is valid. /// Buffer containing the utf8 buffer. public static int InvalidIndex (byte [] buffer) { @@ -526,7 +537,7 @@ public static int InvalidIndex (byte [] buffer) /// /// ValidRune reports whether a rune can be legally encoded as UTF-8. /// - /// true, if rune was valided, false otherwise. + /// true, if rune was validated, false otherwise. /// The rune to test. public static bool ValidRune (Rune rune) { @@ -547,7 +558,7 @@ public static bool ValidRune (Rune rune) /// /// IsGraphic reports whether the rune is defined as a Graphic by Unicode. /// - /// true, if the rune is a lower case lette, false otherwise. + /// true, if the rune is a lower case letter, false otherwise. /// The rune to test for. /// /// Such characters include letters, marks, numbers, punctuation, symbols, and @@ -558,7 +569,7 @@ public static bool ValidRune (Rune rune) /// /// IsPrint reports whether the rune is defined as printable. /// - /// true, if the rune is a lower case lette, false otherwise. + /// true, if the rune is a lower case letter, false otherwise. /// The rune to test for. /// /// Such characters include letters, marks, numbers, punctuation, symbols, and the @@ -572,7 +583,7 @@ public static bool ValidRune (Rune rune) /// /// IsControl reports whether the rune is a control character. /// - /// true, if the rune is a lower case lette, false otherwise. + /// true, if the rune is a lower case letter, false otherwise. /// The rune to test for. /// /// The C (Other) Unicode category includes more code points such as surrogates; use C.InRange (r) to test for them. @@ -598,7 +609,7 @@ public static bool ValidRune (Rune rune) public static bool IsLetterOrDigit (Rune rune) => NStack.Unicode.IsLetter (rune.value) || NStack.Unicode.IsDigit (rune.value); /// - /// IsLetterOrDigit reports whether the rune is a letter (category L) or a number (caetegory N). + /// IsLetterOrDigit reports whether the rune is a letter (category L) or a number (category N). /// /// true, if the rune is a letter or number, false otherwise. /// The rune to test for. @@ -658,21 +669,21 @@ public static bool ValidRune (Rune rune) /// /// Reports whether the rune is an upper case letter. /// - /// true, if the rune is an upper case lette, false otherwise. + /// true, if the rune is an upper case letter, false otherwise. /// The rune to test for. public static bool IsUpper (Rune rune) => NStack.Unicode.IsUpper (rune.value); /// /// Reports whether the rune is a lower case letter. /// - /// true, if the rune is a lower case lette, false otherwise. + /// true, if the rune is a lower case letter, false otherwise. /// The rune to test for. public static bool IsLower (Rune rune) => NStack.Unicode.IsLower (rune.value); /// /// Reports whether the rune is a title case letter. /// - /// true, if the rune is a lower case lette, false otherwise. + /// true, if the rune is a lower case letter, false otherwise. /// The rune to test for. public static bool IsTitle (Rune rune) => NStack.Unicode.IsTitle (rune.value); @@ -691,9 +702,9 @@ public enum Case { Lower = 1, /// - /// Titlecase capitalizes the first letter, and keeps the rest in lowercase. + /// Title case capitalizes the first letter, and keeps the rest in lowercase. /// Sometimes it is not as straight forward as the uppercase, some characters require special handling, like - /// certain ligatures and greek characters. + /// certain ligatures and Greek characters. /// Title = 2 }; diff --git a/NStack/unicode/Rune.extensions.cs b/NStack/unicode/Rune.extensions.cs index 41ee274..3b00a5a 100644 --- a/NStack/unicode/Rune.extensions.cs +++ b/NStack/unicode/Rune.extensions.cs @@ -46,7 +46,7 @@ public static bool FullRune (ustring str) /// /// ustring to decode. /// Starting offset to look into.. - /// Number of bytes valid in the buffer, or -1 to make it the lenght of the buffer. + /// Number of bytes valid in the buffer, or -1 to make it the length of the buffer. public static (Rune rune, int size) DecodeRune (ustring str, int start = 0, int n = -1) { if ((object)str == null) @@ -106,7 +106,7 @@ public static (Rune rune, int size) DecodeRune (ustring str, int start = 0, int /// it returns (RuneError, 0). Otherwise, if /// the encoding is invalid, it returns (RuneError, 1). Both are impossible /// results for correct, non-empty UTF-8. - /// Scan up to that point, if the value is -1, it sets the value to the lenght of the buffer. + /// Scan up to that point, if the value is -1, it sets the value to the length of the buffer. /// /// An encoding is invalid if it is incorrect UTF-8, encodes a rune that is /// out of range, or is not the shortest possible UTF-8 encoding for the @@ -153,7 +153,7 @@ public static (Rune rune, int size) DecodeLastRune (ustring str, int end = -1) /// /// Returns the number of runes in a ustring. /// - /// Numnber of runes. + /// Number of runes. /// utf8 string. public static int RuneCount (ustring str) { @@ -216,7 +216,7 @@ public static int RuneCount (ustring str) /// /// Use to find the index of the first invalid utf8 byte sequence in a buffer /// - /// The index of the first insvalid byte sequence or -1 if the entire buffer is valid. + /// The index of the first invalid byte sequence or -1 if the entire buffer is valid. /// String containing the utf8 buffer. public static int InvalidIndex (ustring str) { diff --git a/NStackTests/RuneTest.cs b/NStackTests/RuneTest.cs index 80aa74e..ecbd2ea 100644 --- a/NStackTests/RuneTest.cs +++ b/NStackTests/RuneTest.cs @@ -14,26 +14,109 @@ public class RuneTest Rune e = '\u1161'; // 0x1161 แ…ก null character with column equal to 0 Rune f = 31; // non printable character Rune g = 127; // non printable character + string h = "\U0001fa01"; + string i = "\U000e0fe1"; + Rune j = '\u20D0'; + Rune k = '\u25a0'; + Rune l = '\u25a1'; [Test] public void TestColumnWidth() { Assert.AreEqual(1, Rune.ColumnWidth(a)); Assert.AreEqual("a", a.ToString()); + Assert.AreEqual(1, a.ToString().Length); + Assert.AreEqual(1, Rune.RuneLen(a)); Assert.AreEqual(1, Rune.ColumnWidth(b)); Assert.AreEqual("b", b.ToString()); - var l = a < b; - Assert.IsTrue(l); + Assert.AreEqual(1, b.ToString().Length); + Assert.AreEqual(1, Rune.RuneLen(b)); + var rl = a < b; + Assert.IsTrue(rl); Assert.AreEqual(1, Rune.ColumnWidth(c)); Assert.AreEqual("{", c.ToString()); + Assert.AreEqual(1, c.ToString().Length); + Assert.AreEqual(1, Rune.RuneLen(c)); Assert.AreEqual(2, Rune.ColumnWidth(d)); Assert.AreEqual("แ…", d.ToString()); + Assert.AreEqual(1, d.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(d)); Assert.AreEqual(0, Rune.ColumnWidth(e)); Assert.AreEqual("แ…ก", e.ToString()); + Assert.AreEqual(1, e.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(e)); Assert.AreEqual(-1, Rune.ColumnWidth(f)); Assert.AreEqual(1, f.ToString().Length); + Assert.AreEqual(1, Rune.RuneLen(f)); Assert.AreEqual(-1, Rune.ColumnWidth(g)); Assert.AreEqual(1, g.ToString().Length); + Assert.AreEqual(1, Rune.RuneLen(g)); + var uh = ustring.Make(h); + (var runeh, var sizeh) = Rune.DecodeRune(uh); + Assert.AreEqual(1, Rune.ColumnWidth(runeh)); + Assert.AreEqual("๐Ÿจ", h); + Assert.AreEqual(2, runeh.ToString().Length); + Assert.AreEqual(4, Rune.RuneLen(runeh)); + Assert.AreEqual(sizeh, Rune.RuneLen(runeh)); + for (int i = 0; i < uh.Length - 1; i++) + { + Assert.False(Rune.DecodeSurrogatePair(uh[i], uh[i + 1]) > 0); + } + Assert.IsTrue(Rune.ValidRune(runeh)); + Assert.True(Rune.Valid(uh.ToByteArray())); + Assert.True(Rune.FullRune(uh.ToByteArray())); + Assert.AreEqual(1, Rune.RuneCount(uh)); + (var runelh, var sizelh) = Rune.DecodeLastRune(uh); + + Assert.AreEqual(1, Rune.ColumnWidth(runelh)); + Assert.AreEqual(2, runelh.ToString().Length); + Assert.AreEqual(4, Rune.RuneLen(runelh)); + Assert.AreEqual(sizelh, Rune.RuneLen(runelh)); + Assert.IsTrue(Rune.ValidRune(runelh)); + + var ui = ustring.Make(i); + (var runei, var sizei) = Rune.DecodeRune(ui); + Assert.AreEqual(1, Rune.ColumnWidth(runei)); + Assert.AreEqual("๓ ฟก", i); + Assert.AreEqual(2, runei.ToString().Length); + Assert.AreEqual(4, Rune.RuneLen(runei)); + Assert.AreEqual(sizei, Rune.RuneLen(runei)); + for (int i = 0; i < ui.Length - 1; i++) + { + Assert.False(Rune.DecodeSurrogatePair(ui[i], ui[i + 1]) > 0); + } + Assert.IsTrue(Rune.ValidRune(runei)); + Assert.True(Rune.Valid(ui.ToByteArray())); + Assert.True(Rune.FullRune(ui.ToByteArray())); + (var runeli, var sizeli) = Rune.DecodeLastRune(ui); + Assert.AreEqual(1, Rune.ColumnWidth(runeli)); + Assert.AreEqual(2, runeli.ToString().Length); + Assert.AreEqual(4, Rune.RuneLen(runeli)); + Assert.AreEqual(sizeli, Rune.RuneLen(runeli)); + Assert.IsTrue(Rune.ValidRune(runeli)); + + Assert.AreEqual(Rune.ColumnWidth(runeh), Rune.ColumnWidth(runei)); + Assert.AreNotEqual(h, i); + Assert.AreEqual(runeh.ToString().Length, runei.ToString().Length); + Assert.AreEqual(Rune.RuneLen(runeh), Rune.RuneLen(runei)); + Assert.AreEqual(Rune.RuneLen(runeh), Rune.RuneLen(runei)); + var uj = ustring.Make(j); + (var runej, var sizej) = Rune.DecodeRune(uj); + Assert.AreEqual(0, Rune.ColumnWidth(j)); + Assert.AreEqual(0, Rune.ColumnWidth(uj.RuneAt (0))); + Assert.AreEqual(j, uj.RuneAt(0)); + Assert.AreEqual(1, j.ToString().Length); + Assert.AreEqual(1, runej.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(j)); + Assert.AreEqual(sizej, Rune.RuneLen(runej)); + Assert.AreEqual(1, Rune.ColumnWidth(k)); + Assert.AreEqual("โ– ", k.ToString()); + Assert.AreEqual(1, k.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(k)); + Assert.AreEqual(1, Rune.ColumnWidth(l)); + Assert.AreEqual("โ–ก", l.ToString()); + Assert.AreEqual(1, l.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(l)); } [Test] @@ -55,6 +138,7 @@ public void TestRune() Assert.AreEqual(1, Rune.ColumnWidth(d)); Assert.AreEqual(2, d.ToString().Length); Assert.AreEqual("๐ก", d.ToString()); + Assert.False(Rune.DecodeSurrogatePair('\ud799', '\udc21') > 0); Assert.Throws(() => new Rune('\ud799', '\udc21')); Rune e = new Rune('\ud801', '\udc21'); Assert.AreEqual(1, Rune.ColumnWidth(e)); @@ -103,6 +187,19 @@ public void TestRune() Assert.AreEqual(1, Rune.ColumnWidth(o)); Assert.AreEqual(2, o.ToString().Length); Assert.AreEqual("๐Ÿ”ฎ", o.ToString()); + var p = new Rune('\u2329'); + Assert.AreEqual(2, Rune.ColumnWidth(p)); + Assert.AreEqual(1, p.ToString().Length); + Assert.AreEqual("โŒฉ", p.ToString()); + var q = new Rune('\u232a'); + Assert.AreEqual(2, Rune.ColumnWidth(q)); + Assert.AreEqual(1, q.ToString().Length); + Assert.AreEqual("โŒช", q.ToString()); + //var r = new Rune('\U0001fa00'); + //Assert.AreEqual(2, Rune.ColumnWidth(r)); + //Assert.AreEqual(1, r.ToString().Length); + //Assert.AreEqual("โŒฉ", r.ToString()); + PrintTextElementCount(ustring.Make('\u00e1'), "รก", 1, 1, 1, 1); PrintTextElementCount(ustring.Make('\u0061', '\u0301'), "aฬ", 1, 2, 2, 1); @@ -145,7 +242,7 @@ int CountLettersInString(string s) int letterCount = 0; var us = ustring.Make(s); - foreach (Rune rune in us.ToRunes()) + foreach (Rune rune in us) { if (Rune.IsLetter(rune)) { letterCount++; } @@ -192,8 +289,9 @@ bool ProcessStringUseRune(string s) { var us = ustring.Make(s); int i = 0; + string rs = ""; - foreach (Rune rune in us.ToRunes()) + foreach (Rune rune in us) { if (rune == Rune.Error) { @@ -201,8 +299,10 @@ bool ProcessStringUseRune(string s) } Assert.IsTrue(Rune.ValidRune(rune)); i += Rune.ColumnWidth(rune); // increment the iterator by the number of chars in this Rune + rs += rune.ToString(); } Assert.AreEqual(us.ConsoleWidth, i); + Assert .AreEqual(s, rs); return true; } @@ -215,5 +315,27 @@ public void TestSplit() Assert.AreEqual(3, splitOnSpace.Length); Assert.AreEqual(3, splitOnComma.Length); } + + [Test] + public void TestValidRune() + { + Assert.IsTrue(Rune.ValidRune(new Rune('\ud83c', '\udf39'))); + Assert.IsFalse(Rune.ValidRune((uint)'\ud801')); // To avoid throwing an exception pass as uint parameter instead Rune. + Assert.Throws(() => Rune.ValidRune('\ud801')); + } + + [Test] + public void TestValid() + { + var rune1 = new Rune('\ud83c', '\udf39'); + var buff1 = new byte[4]; + Assert.AreEqual(4, Rune.EncodeRune(rune1, buff1)); + Assert.IsTrue(Rune.Valid(buff1)); + var rune2 = (uint)'\ud801'; // To avoid throwing an exception set as uint instead a Rune instance. + var buff2 = new byte[4]; + Assert.AreEqual(3, Rune.EncodeRune(rune2, buff2)); + Assert.IsFalse(Rune.Valid(buff2)); // To avoid throwing an exception pass as uint parameter instead Rune. + Assert.Throws(() => Rune.EncodeRune(new Rune('\ud801'), buff2)); + } } } From 35e1471f98ee3da71c1c79eae4cb414dab06eaf9 Mon Sep 17 00:00:00 2001 From: BDisp Date: Tue, 10 Nov 2020 21:31:07 +0000 Subject: [PATCH 3/6] More unit test for UTF-8, UTF-16 and UTF-32 Encoding. --- NStackTests/RuneTest.cs | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/NStackTests/RuneTest.cs b/NStackTests/RuneTest.cs index ecbd2ea..b40b852 100644 --- a/NStackTests/RuneTest.cs +++ b/NStackTests/RuneTest.cs @@ -19,6 +19,10 @@ public class RuneTest Rune j = '\u20D0'; Rune k = '\u25a0'; Rune l = '\u25a1'; + Rune m = '\uf61e'; + byte[] n = new byte[4] { 0xf0, 0x9f, 0x8d, 0x95 }; // UTF-8 Encoding + Rune o = new Rune('\ud83c', '\udf55'); // UTF-16 Encoding; + string p = "\U0001F355"; // UTF-32 Encoding [Test] public void TestColumnWidth() @@ -117,6 +121,24 @@ public void TestColumnWidth() Assert.AreEqual("โ–ก", l.ToString()); Assert.AreEqual(1, l.ToString().Length); Assert.AreEqual(3, Rune.RuneLen(l)); + Assert.AreEqual(1, Rune.ColumnWidth(m)); + Assert.AreEqual("๏˜ž", m.ToString()); + Assert.AreEqual(1, m.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(m)); + var rn = Rune.DecodeRune(ustring.Make(n)).rune; + Assert.AreEqual(1, Rune.ColumnWidth(rn)); + Assert.AreEqual("๐Ÿ•", rn.ToString()); + Assert.AreEqual(2, rn.ToString().Length); + Assert.AreEqual(4, Rune.RuneLen(rn)); + Assert.AreEqual(1, Rune.ColumnWidth(o)); + Assert.AreEqual("๐Ÿ•", o.ToString()); + Assert.AreEqual(2, o.ToString().Length); + Assert.AreEqual(4, Rune.RuneLen(o)); + var rp = Rune.DecodeRune(ustring.Make(p)).rune; + Assert.AreEqual(1, Rune.ColumnWidth(rp)); + Assert.AreEqual("๐Ÿ•", p); + Assert.AreEqual(2, p.Length); + Assert.AreEqual(4, Rune.RuneLen(rp)); } [Test] @@ -331,10 +353,14 @@ public void TestValid() var buff1 = new byte[4]; Assert.AreEqual(4, Rune.EncodeRune(rune1, buff1)); Assert.IsTrue(Rune.Valid(buff1)); + Assert.AreEqual(2, rune1.ToString().Length); + Assert.AreEqual(4, Rune.RuneLen(rune1)); var rune2 = (uint)'\ud801'; // To avoid throwing an exception set as uint instead a Rune instance. var buff2 = new byte[4]; Assert.AreEqual(3, Rune.EncodeRune(rune2, buff2)); Assert.IsFalse(Rune.Valid(buff2)); // To avoid throwing an exception pass as uint parameter instead Rune. + Assert.AreEqual(5, rune2.ToString().Length); // Invalid string. It returns the decimal 55297 representation of the 0xd801 hexadecimal. + Assert.AreEqual(-1, Rune.RuneLen(rune2)); Assert.Throws(() => Rune.EncodeRune(new Rune('\ud801'), buff2)); } } From e7fc25780d88f34ff701c63d57ec54acbfba1b8a Mon Sep 17 00:00:00 2001 From: BDisp Date: Sun, 22 Nov 2020 22:12:56 +0000 Subject: [PATCH 4/6] Added more rune ranges in the ColumnWidth method and more unit tests. --- NStack/unicode/Rune.ColumnWidth.cs | 7 ++--- NStackTests/RuneTest.cs | 42 ++++++++++++++++++++++++------ 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/NStack/unicode/Rune.ColumnWidth.cs b/NStack/unicode/Rune.ColumnWidth.cs index 66489e8..0f9f40b 100644 --- a/NStack/unicode/Rune.ColumnWidth.cs +++ b/NStack/unicode/Rune.ColumnWidth.cs @@ -98,15 +98,16 @@ public static int ColumnWidth (Rune rune) return 1 + ((irune >= 0x1100 && (irune <= 0x115f || /* Hangul Jamo init. consonants */ - irune == 0x2329 || irune == 0x232a || + irune == 0x2329 || irune == 0x232a || /* Miscellaneous Technical */ (irune >= 0x2e80 && irune <= 0xa4cf && - irune != 0x303f) || /* CJK ... Yi */ + irune != 0x303f) || /* CJK ... Yi */ (irune >= 0xac00 && irune <= 0xd7a3) || /* Hangul Syllables */ (irune >= 0xf900 && irune <= 0xfaff) || /* CJK Compatibility Ideographs */ (irune >= 0xfe10 && irune <= 0xfe19) || /* Vertical forms */ (irune >= 0xfe30 && irune <= 0xfe6f) || /* CJK Compatibility Forms */ (irune >= 0xff00 && irune <= 0xff60) || /* Fullwidth Forms */ - (irune >= 0xffe0 && irune <= 0xffe6) || + (irune >= 0xffe0 && irune <= 0xffe6) || /* Alphabetic Presentation Forms*/ + (irune >= 0x1fa00 && irune <= 0x1facf) || /* Chess Symbols*/ (irune >= 0x20000 && irune <= 0x2fffd) || (irune >= 0x30000 && irune <= 0x3fffd))) ? 1 : 0); } diff --git a/NStackTests/RuneTest.cs b/NStackTests/RuneTest.cs index b40b852..44f1bf7 100644 --- a/NStackTests/RuneTest.cs +++ b/NStackTests/RuneTest.cs @@ -23,6 +23,9 @@ public class RuneTest byte[] n = new byte[4] { 0xf0, 0x9f, 0x8d, 0x95 }; // UTF-8 Encoding Rune o = new Rune('\ud83c', '\udf55'); // UTF-16 Encoding; string p = "\U0001F355"; // UTF-32 Encoding + Rune q = '\u2103'; + Rune r = '\u1100'; + Rune s = '\u2501'; [Test] public void TestColumnWidth() @@ -57,7 +60,7 @@ public void TestColumnWidth() Assert.AreEqual(1, Rune.RuneLen(g)); var uh = ustring.Make(h); (var runeh, var sizeh) = Rune.DecodeRune(uh); - Assert.AreEqual(1, Rune.ColumnWidth(runeh)); + Assert.AreEqual(2, Rune.ColumnWidth(runeh)); Assert.AreEqual("๐Ÿจ", h); Assert.AreEqual(2, runeh.ToString().Length); Assert.AreEqual(4, Rune.RuneLen(runeh)); @@ -72,7 +75,7 @@ public void TestColumnWidth() Assert.AreEqual(1, Rune.RuneCount(uh)); (var runelh, var sizelh) = Rune.DecodeLastRune(uh); - Assert.AreEqual(1, Rune.ColumnWidth(runelh)); + Assert.AreEqual(2, Rune.ColumnWidth(runelh)); Assert.AreEqual(2, runelh.ToString().Length); Assert.AreEqual(4, Rune.RuneLen(runelh)); Assert.AreEqual(sizelh, Rune.RuneLen(runelh)); @@ -99,7 +102,7 @@ public void TestColumnWidth() Assert.AreEqual(sizeli, Rune.RuneLen(runeli)); Assert.IsTrue(Rune.ValidRune(runeli)); - Assert.AreEqual(Rune.ColumnWidth(runeh), Rune.ColumnWidth(runei)); + Assert.AreNotEqual(Rune.ColumnWidth(runeh), Rune.ColumnWidth(runei)); Assert.AreNotEqual(h, i); Assert.AreEqual(runeh.ToString().Length, runei.ToString().Length); Assert.AreEqual(Rune.RuneLen(runeh), Rune.RuneLen(runei)); @@ -109,6 +112,8 @@ public void TestColumnWidth() Assert.AreEqual(0, Rune.ColumnWidth(j)); Assert.AreEqual(0, Rune.ColumnWidth(uj.RuneAt (0))); Assert.AreEqual(j, uj.RuneAt(0)); + Assert.AreEqual("โƒ", j.ToString()); + Assert.AreEqual("โƒ", uj.ToString()); Assert.AreEqual(1, j.ToString().Length); Assert.AreEqual(1, runej.ToString().Length); Assert.AreEqual(3, Rune.RuneLen(j)); @@ -139,6 +144,28 @@ public void TestColumnWidth() Assert.AreEqual("๐Ÿ•", p); Assert.AreEqual(2, p.Length); Assert.AreEqual(4, Rune.RuneLen(rp)); + Assert.AreEqual(1, Rune.ColumnWidth(q)); + Assert.AreEqual("โ„ƒ", q.ToString()); + Assert.AreEqual(1, q.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(q)); + var rq = Rune.DecodeRune(ustring.Make(q)).rune; + Assert.AreEqual(1, Rune.ColumnWidth(rq)); + Assert.AreEqual("โ„ƒ", rq.ToString()); + Assert.AreEqual(1, rq.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(rq)); + Assert.AreEqual(2, Rune.ColumnWidth(r)); + Assert.AreEqual("แ„€", r.ToString()); + Assert.AreEqual(1, r.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(r)); + Assert.AreEqual(1, Rune.ColumnWidth(s)); + Assert.AreEqual("โ”", s.ToString()); + Assert.AreEqual(1, s.ToString().Length); + Assert.AreEqual(3, Rune.RuneLen(s)); + var buff = new byte[4]; + var sb = Rune.EncodeRune('\u2503', buff); + var scb = char.ConvertToUtf32("โ„ƒ", 0); + var scr = 'โ„ƒ'.ToString().Length; + } [Test] @@ -217,11 +244,10 @@ public void TestRune() Assert.AreEqual(2, Rune.ColumnWidth(q)); Assert.AreEqual(1, q.ToString().Length); Assert.AreEqual("โŒช", q.ToString()); - //var r = new Rune('\U0001fa00'); - //Assert.AreEqual(2, Rune.ColumnWidth(r)); - //Assert.AreEqual(1, r.ToString().Length); - //Assert.AreEqual("โŒฉ", r.ToString()); - + var r = Rune.DecodeRune(ustring.Make("\U0000232a")).rune; + Assert.AreEqual(2, Rune.ColumnWidth(r)); + Assert.AreEqual(1, r.ToString().Length); + Assert.AreEqual("โŒช", r.ToString()); PrintTextElementCount(ustring.Make('\u00e1'), "รก", 1, 1, 1, 1); PrintTextElementCount(ustring.Make('\u0061', '\u0301'), "aฬ", 1, 2, 2, 1); From 9bb6d60e0bb7c09d25924c7c1efc7cb26e32691c Mon Sep 17 00:00:00 2001 From: BDisp Date: Mon, 23 Nov 2020 13:00:05 +0000 Subject: [PATCH 5/6] Adjusting some formatting. --- NStack/unicode/Rune.ColumnWidth.cs | 2 +- NStackTests/RuneTest.cs | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/NStack/unicode/Rune.ColumnWidth.cs b/NStack/unicode/Rune.ColumnWidth.cs index 0f9f40b..8e71a14 100644 --- a/NStack/unicode/Rune.ColumnWidth.cs +++ b/NStack/unicode/Rune.ColumnWidth.cs @@ -98,7 +98,7 @@ public static int ColumnWidth (Rune rune) return 1 + ((irune >= 0x1100 && (irune <= 0x115f || /* Hangul Jamo init. consonants */ - irune == 0x2329 || irune == 0x232a || /* Miscellaneous Technical */ + irune == 0x2329 || irune == 0x232a || /* Miscellaneous Technical */ (irune >= 0x2e80 && irune <= 0xa4cf && irune != 0x303f) || /* CJK ... Yi */ (irune >= 0xac00 && irune <= 0xd7a3) || /* Hangul Syllables */ diff --git a/NStackTests/RuneTest.cs b/NStackTests/RuneTest.cs index 44f1bf7..156ef61 100644 --- a/NStackTests/RuneTest.cs +++ b/NStackTests/RuneTest.cs @@ -165,7 +165,6 @@ public void TestColumnWidth() var sb = Rune.EncodeRune('\u2503', buff); var scb = char.ConvertToUtf32("โ„ƒ", 0); var scr = 'โ„ƒ'.ToString().Length; - } [Test] From 6b2b3d42f46f3d01e3c9f89afbd3e27693ede634 Mon Sep 17 00:00:00 2001 From: BDisp Date: Thu, 26 Nov 2020 11:36:46 +0000 Subject: [PATCH 6/6] Reverting the removed System.ValueTuple. --- NStack/NStack.csproj | 5 ++++- NStack/packages.config | 2 +- NStackTests/NStackTests.csproj | 3 +++ NStackTests/packages.config | 1 + 4 files changed, 9 insertions(+), 2 deletions(-) diff --git a/NStack/NStack.csproj b/NStack/NStack.csproj index f4f94ac..c9d7c9f 100644 --- a/NStack/NStack.csproj +++ b/NStack/NStack.csproj @@ -46,5 +46,8 @@ Added ustring.ColumnWidth to return number of columns that a ustring takes in a true Latest - + + + + diff --git a/NStack/packages.config b/NStack/packages.config index 0b4e04e..0c20558 100644 --- a/NStack/packages.config +++ b/NStack/packages.config @@ -1,5 +1,5 @@ ๏ปฟ - + \ No newline at end of file diff --git a/NStackTests/NStackTests.csproj b/NStackTests/NStackTests.csproj index 4a1b904..8b6c3bc 100644 --- a/NStackTests/NStackTests.csproj +++ b/NStackTests/NStackTests.csproj @@ -35,6 +35,9 @@ ..\packages\NUnit.3.12.0\lib\net45\nunit.framework.dll + + ..\packages\System.ValueTuple.4.5.0\lib\net461\System.ValueTuple.dll + diff --git a/NStackTests/packages.config b/NStackTests/packages.config index 20d68e1..2c21c55 100644 --- a/NStackTests/packages.config +++ b/NStackTests/packages.config @@ -1,4 +1,5 @@ ๏ปฟ + \ No newline at end of file