diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 341e246..9f3947f 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -10,8 +10,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - dotnet: ["3.1.404", "5.0.101"] - + dotnet: ["6.0.x"] steps: - uses: actions/checkout@v2 - name: Setup .NET diff --git a/.github/workflows/pkgrelease.yml b/.github/workflows/pkgrelease.yml index 647ec0a..373d465 100644 --- a/.github/workflows/pkgrelease.yml +++ b/.github/workflows/pkgrelease.yml @@ -9,11 +9,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - name: Setup dotnet uses: actions/setup-dotnet@v1 with: - dotnet-version: 3.1.404 + dotnet-version: "6.0.x" - name: Install dependencies run: dotnet restore - name: Package diff --git a/README.md b/README.md index 3d180c1..12bd82a 100644 --- a/README.md +++ b/README.md @@ -14,15 +14,13 @@ See [usage.md](./usage.md). ## Known issues -- Skipping comments is a little buggy. +- Skipping comments is a little unpredictable. ## Requirements -### Prerequisites -- [.NET](https://dotnet.microsoft.com/download) 5+ or Core 3.1 -- [.NET Interactive](https://github.com/dotnet/interactive/blob/main/README.md) for notebooks - - [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) (does not require Jupyter) - - [nteract](https://nteract.io/) (requires Jupyter) +- [.NET](https://dotnet.microsoft.com/download) 6+. +- [.NET Interactive](https://github.com/dotnet/interactive/blob/main/README.md) for notebooks (optional). + - [VSCode Extension](https://marketplace.visualstudio.com/items?itemName=ms-dotnettools.dotnet-interactive-vscode) or [nteract](https://nteract.io/) ## License diff --git a/Sixam.CST.Tests/GlobalUsings.cs b/Sixam.CST.Tests/GlobalUsings.cs new file mode 100644 index 0000000..cacb05a --- /dev/null +++ b/Sixam.CST.Tests/GlobalUsings.cs @@ -0,0 +1,3 @@ +// This project is licensed under the MIT license. +// See the LICENSE file in the project root for more information. +global using Xunit; \ No newline at end of file diff --git a/Sixam.CST.Tests/MultilineTests.cs b/Sixam.CST.Tests/MultilineTests.cs index e1a0c5e..55e4de0 100644 --- a/Sixam.CST.Tests/MultilineTests.cs +++ b/Sixam.CST.Tests/MultilineTests.cs @@ -1,28 +1,25 @@ // This project is licensed under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using Xunit; -namespace Sixam.CST.Tests +namespace Sixam.CST.Tests; + +public class MultilineTests { - public class MultilineTests + [Fact] + public void MiltilineV1() { - [Fact] - public void MiltilineV1() - { - var expected = $"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce justo dui, rhoncus a pulvinar sit amet, fermentum vitae lorem. Maecenas nec nisi sit amet eros rutrum congue. In sagittis suscipit arcu, ac vestibulum nunc feugiat volutpat.{Environment.NewLine}{Environment.NewLine}Vivamus consequat velit dui, sit amet rhoncus dui malesuada a. Maecenas hendrerit commodo mi et scelerisque. Cras pharetra ultrices aliquam. Praesent ac efficitur magna, vitae scelerisque metus."; - var lorem = new UIText("lorem"); - var actual = lorem.GetText(101, 4); - Assert.Equal(expected, actual); - } + var expected = $"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Fusce justo dui, rhoncus a pulvinar sit amet, fermentum vitae lorem. Maecenas nec nisi sit amet eros rutrum congue. In sagittis suscipit arcu, ac vestibulum nunc feugiat volutpat.{Environment.NewLine}{Environment.NewLine}Vivamus consequat velit dui, sit amet rhoncus dui malesuada a. Maecenas hendrerit commodo mi et scelerisque. Cras pharetra ultrices aliquam. Praesent ac efficitur magna, vitae scelerisque metus."; + var lorem = new UIText("lorem"); + var actual = lorem.GetText(101, 4); + Assert.Equal(expected, actual); + } - [Fact] - public void MiltilineV2() - { - var expected = $"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc gravida nunc non justo pretium consectetur. Sed tempus libero ac ligula aliquam elementum. Duis vitae interdum leo. Sed semper nulla %1 a lectus dictum dictum.{Environment.NewLine}{Environment.NewLine}Quisque vehicula, nisi ut scelerisque sodales, nisi ipsum sodales ipsum, in rutrum tellus lacus sed nibh. Etiam mauris velit, elementum sed placerat et, elementum et tellus. Duis vitae elit fermentum, viverra lorem in, lobortis elit."; - var lorem = new UIText("lorem"); - var actual = lorem.GetText(102, "Multiline"); - Assert.Equal(expected, actual); - } + [Fact] + public void MiltilineV2() + { + var expected = $"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc gravida nunc non justo pretium consectetur. Sed tempus libero ac ligula aliquam elementum. Duis vitae interdum leo. Sed semper nulla %1 a lectus dictum dictum.{Environment.NewLine}{Environment.NewLine}Quisque vehicula, nisi ut scelerisque sodales, nisi ipsum sodales ipsum, in rutrum tellus lacus sed nibh. Etiam mauris velit, elementum sed placerat et, elementum et tellus. Duis vitae elit fermentum, viverra lorem in, lobortis elit."; + var lorem = new UIText("lorem"); + var actual = lorem.GetText(102, "Multiline"); + Assert.Equal(expected, actual); } } diff --git a/Sixam.CST.Tests/SingleLineTests.cs b/Sixam.CST.Tests/SingleLineTests.cs index 03b9a9b..3e33aaa 100644 --- a/Sixam.CST.Tests/SingleLineTests.cs +++ b/Sixam.CST.Tests/SingleLineTests.cs @@ -1,28 +1,26 @@ // This project is licensed under the MIT license. // See the LICENSE file in the project root for more information. -using Xunit; -namespace Sixam.CST.Tests +namespace Sixam.CST.Tests; + +public class SingleLineTests { - public class SingleLineTests + [Theory] + [InlineData(1, @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ac dictum orci, at tincidunt nulla. Donec aliquet, eros non interdum posuere, ipsum sapien molestie nunc, nec facilisis libero ipsum et risus. In sed lorem vel ipsum placerat viverra.")] + [InlineData(3, @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam venenatis ac odio ut pretium. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec semper turpis tempor, bibendum sapien at, blandit neque. Vivamus hendrerit imperdiet elit, vel sollicitudin nulla luctus vel. Vivamus nisl quam, feugiat a diam aliquam, iaculis vestibulum nunc. Maecenas euismod leo enim, faucibus ultrices ipsum semper eu. Praesent ullamcorper justo at maximus ultricies.")] + public void V1Test(int key, string expected) { - [Theory] - [InlineData(1, @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ac dictum orci, at tincidunt nulla. Donec aliquet, eros non interdum posuere, ipsum sapien molestie nunc, nec facilisis libero ipsum et risus. In sed lorem vel ipsum placerat viverra.")] - [InlineData(3, @"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam venenatis ac odio ut pretium. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec semper turpis tempor, bibendum sapien at, blandit neque. Vivamus hendrerit imperdiet elit, vel sollicitudin nulla luctus vel. Vivamus nisl quam, feugiat a diam aliquam, iaculis vestibulum nunc. Maecenas euismod leo enim, faucibus ultrices ipsum semper eu. Praesent ullamcorper justo at maximus ultricies.")] - public void V1Test(int key, string expected) - { - var lorem = new UIText("lorem"); - var actual = lorem.GetText(101, key); - Assert.Equal(expected, actual); - } + var lorem = new UIText("lorem"); + var actual = lorem.GetText(101, key); + Assert.Equal(expected, actual); + } - [Theory] - [InlineData("Singleline", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ultricies nulla eu tortor mattis, dictum posuere lacus ornare. Maecenas a massa in ligula finibus luctus eu vitae nibh. Proin imperdiet dapibus mauris quis placerat.")] - public void V2Test(string key, string expected) - { - var lorem = new UIText("lorem"); - var actual = lorem.GetText(102, key); - Assert.Equal(expected, actual); - } + [Theory] + [InlineData("Singleline", "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed ultricies nulla eu tortor mattis, dictum posuere lacus ornare. Maecenas a massa in ligula finibus luctus eu vitae nibh. Proin imperdiet dapibus mauris quis placerat.")] + public void V2Test(string key, string expected) + { + var lorem = new UIText("lorem"); + var actual = lorem.GetText(102, key); + Assert.Equal(expected, actual); } } diff --git a/Sixam.CST.Tests/Sixam.CST.Tests.csproj b/Sixam.CST.Tests/Sixam.CST.Tests.csproj index 7baa57a..f6f5c65 100644 --- a/Sixam.CST.Tests/Sixam.CST.Tests.csproj +++ b/Sixam.CST.Tests/Sixam.CST.Tests.csproj @@ -1,7 +1,9 @@ - netcoreapp3.1 + net6.0 + enable + enable false diff --git a/Sixam.CST/CaretSeparatedText.cs b/Sixam.CST/CaretSeparatedText.cs index 1346f67..2fc61ce 100644 --- a/Sixam.CST/CaretSeparatedText.cs +++ b/Sixam.CST/CaretSeparatedText.cs @@ -1,90 +1,80 @@ // This project is licensed under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -namespace Sixam.CST +namespace Sixam.CST; + +public class CaretSeparatedText { - public class CaretSeparatedText + const char CARET = '^'; + const string LF = "\u000A"; + const string CR = "\u000D"; + const string CRLF = "\u000D\u000A"; + const string LS = "\u2028"; + + /// + /// Gets the value from the digit-based key. + /// + /// Returns the entry + public static string Parse(string content, int key) => Parse(content, key.ToString()); + + /// + /// Gets the value from the string-based key. + /// + /// Returns the entry + public static string Parse(string content, string key) { - const char CARET = '^'; - const string LF = "\u000A"; - const string CR = "\u000D"; - const string CRLF = "\u000D\u000A"; - const string LS = "\u2028"; + var entries = NormalizeEntries(content); + return GetEntry(entries, key); + } - /// - /// Gets the value from the digit-based key. - /// - /// Returns the entry - public static string Parse(string content, int key) => Parse(content, key.ToString()); - - /// - /// Gets the value from the string-based key. - /// - /// Returns the entry - public static string Parse(string content, string key) + /// + /// Replaces the document's line endings with the native system line endings. + /// + /// This stage ensures there are no crashes during parsing. + static IEnumerable NormalizeEntries(string content) + { + if (!content.Contains(Environment.NewLine)) { - var entries = NormalizeEntries(content); - return GetEntry(entries, key); + if (content.Contains(LF)) + content = content.Replace(LF, Environment.NewLine); + + if (content.Contains(CR)) + content = content.Replace(CR, Environment.NewLine); + + if (content.Contains(CRLF)) + content = content.Replace(CRLF, Environment.NewLine); + + if (content.Contains(LS)) + content = content.Replace(LS, Environment.NewLine); } - /// - /// Replaces the document's line endings with the native system line endings. - /// - /// This stage ensures there are no crashes during parsing. - static IEnumerable NormalizeEntries(string content) + var lines = content.Split(new[] { $"{CARET}{Environment.NewLine}" }, + StringSplitOptions.RemoveEmptyEntries); + + return lines.Where(line => + !line.StartsWith("//") && + !line.StartsWith("#") && + !line.StartsWith("/*") && + !line.EndsWith("*/")) + .AsEnumerable(); + } + + static string GetEntry(IEnumerable entries, string key) + { + // Search through list + foreach (var entry in entries) { - if (!content.Contains(Environment.NewLine)) - { - if (content.Contains(LF)) - content = content.Replace(LF, Environment.NewLine); + // If the line doesn't start with the key, keep searching. + if (!entry.StartsWith(key)) + continue; - if (content.Contains(CR)) - content = content.Replace(CR, Environment.NewLine); + // Locate index, trim carets and return translation. + var startIndex = entry.IndexOf(CARET); + var line = entry.Substring(startIndex); - if (content.Contains(CRLF)) - content = content.Replace(CRLF, Environment.NewLine); - - if (content.Contains(LS)) - content = content.Replace(LS, Environment.NewLine); - } - - var lines = content.Split(new[] { $"{CARET}{Environment.NewLine}" }, - StringSplitOptions.RemoveEmptyEntries); - - return lines.Where(line => - !line.StartsWith("//") && - !line.StartsWith("#") && - !line.StartsWith("/*") && - !line.EndsWith("*/")) - .AsEnumerable(); + return line.TrimStart(CARET).TrimEnd(CARET); } - static string GetEntry(IEnumerable entries, string key) - { - // Search through list - foreach (var entry in entries) - { - // If the line doesn't start with the key, keep searching. - if (!entry.StartsWith(key)) - continue; - - // Locate index, trim carets and return translation. - var startIndex = entry.IndexOf(CARET); - var line = entry.Substring(startIndex); - - return line.TrimStart(CARET).TrimEnd(CARET); - } - - return "***MISSING***"; - } + return "***MISSING***"; } } - -namespace CSTNet -{ - [Obsolete("Use the Sixam.CST namespace instead.")] - public class CaretSeparatedText : Sixam.CST.CaretSeparatedText { } -} diff --git a/Sixam.CST/Sixam.CST.csproj b/Sixam.CST/Sixam.CST.csproj index 65d3755..6f26ba6 100644 --- a/Sixam.CST/Sixam.CST.csproj +++ b/Sixam.CST/Sixam.CST.csproj @@ -1,8 +1,10 @@ - netstandard2.0 - 1.1.100 + net6.0 + 1.2.100 + enable + enable Tony Bark, Sixam Software Caret-Separated Text (or CST) is a key-value pair format represented by digits or words as keys and the value as text enclosed between carets. ([key] ^[value]^) diff --git a/Sixam.CST/UIText.cs b/Sixam.CST/UIText.cs index b091f04..0a483be 100644 --- a/Sixam.CST/UIText.cs +++ b/Sixam.CST/UIText.cs @@ -1,57 +1,54 @@ // This project is licensed under the MIT license. // See the LICENSE file in the project root for more information. -using System; -using System.IO; -namespace Sixam.CST +namespace Sixam.CST; + +public class UIText { - public class UIText + string Language { get; set; } = "english"; + public string[] BasePath { get; set; } = { AppContext.BaseDirectory, "uitext" }; + + public UIText() { } + + public UIText(string language) { - string Language { get; set; } = "english"; - public string[] BasePath { get; set; } = { AppContext.BaseDirectory, "uitext" }; + Language = language; + } - public UIText() { } + public UIText(string language, params string[] baseBath) + { + Language = language; + BasePath = baseBath; + } - public UIText(string language) + public string GetText(int id, int key) => GetText(id, key.ToString()); + + public string GetText(int id, string key) + { + var basePath = Path.Combine(BasePath); + var langPath = Path.Combine(basePath, $"{Language}.dir"); + var files = Directory.GetFiles(langPath); + + foreach (var file in files) { - Language = language; + if (!file.Contains(".cst")) + continue; + + var ids = Path.GetFileName(file); + var second = ids.IndexOf("_", 1, StringComparison.InvariantCultureIgnoreCase); + + if (second == -1) + continue; + + ids = ids.Substring(1, second - 1); + + if (ids != id.ToString()) + continue; + + var content = File.ReadAllText(file); + return CaretSeparatedText.Parse(content, key); } - public UIText(string language, params string[] baseBath) - { - Language = language; - BasePath = baseBath; - } - - public string GetText(int id, int key) => GetText(id, key.ToString()); - - public string GetText(int id, string key) - { - var basePath = Path.Combine(BasePath); - var langPath = Path.Combine(basePath, $"{Language}.dir"); - var files = Directory.GetFiles(langPath); - - foreach (var file in files) - { - if (!file.Contains(".cst")) - continue; - - var ids = Path.GetFileName(file); - var second = ids.IndexOf("_", 1, StringComparison.InvariantCultureIgnoreCase); - - if (second == -1) - continue; - - ids = ids.Substring(1, second - 1); - - if (ids != id.ToString()) - continue; - - var content = File.ReadAllText(file); - return CaretSeparatedText.Parse(content, key); - } - - return "***MISSING***"; - } + return "***MISSING***"; } } diff --git a/changelog.md b/changelog.md index 469d05b..3f8a169 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,19 @@ # Change Log +## 1.2.100 + +This version removes support for .NET Standard 2.0 in favor of .NET 6 and brings with it (much needed) quality of life changes to the project. Apart from the removal of the ``CSTNet`` namespace, nothing has changed to the API itself and you can continue to use to 1.1.100 on all platforms where .NET Standard 2.0 is [supported](https://dotnet.microsoft.com/platform/dotnet-standard). + +From 1.2 onward, Sixam.CST will only target LTS releases. This is why .NET 5 was skipped, despite the initial platform unification. + +### Project Changes + +With the move to .NET 6.0, this version brings with it a lot of quality of life changes to the project. This includes file-scoped namespaces, implicit and global usings. For a full list of language changes see [Welcome to C# 10](https://devblogs.microsoft.com/dotnet/welcome-to-csharp-10/). + +### Nullable Reference Types + +The only significant architectural change that was finally enabled with this release is [nullable reference types](https://docs.microsoft.com/en-us/dotnet/csharp/whats-new/tutorials/nullable-reference-types). This feature was introduced in .NET Core 3.0 and .NET Standard 2.1, respectfully, but not the .NET Framework. Nullable values are already taken care of by the library. The reason why I never switched to 2.1 before was because the Sims community has historically targeted the .NET Framework, and that doesn't support 2.1. + ## 1.1.100 - Switched to Sixam.CST namespace and marked CSTNet namespace as obsolete. @@ -39,4 +53,4 @@ For point releases (such as this), Sixam.CST will remain under the CSTNet namesp ## 1.0.0 -- Initial release. \ No newline at end of file +- Initial release. diff --git a/usage.md b/usage.md index 0c8cfdb..c108b41 100644 --- a/usage.md +++ b/usage.md @@ -7,9 +7,7 @@ ``` ```csharp -#r "nuget:Sixam.CST,1.1" -using System; -using System.IO; +#r "nuget:Sixam.CST,1.1.100" // If using notebooks using Sixam.CST; var file = File.ReadAllText("example.cst"); @@ -18,31 +16,29 @@ var example = CaretSeparatedText.Parse(file, 1); Console.WriteLine(example); ``` -See working example on [.NET Fiddle](https://dotnetfiddle.net/ecKb2h). - ## Complex Scenarios -In production, CST files were used in The Sims Online (TSO) to provide translations. It was required that they were prefixed with numbers enclosed in underscores, known as the ID. The IDs were used to locate the right file without knowing it's name. Meanwhile, each translation was split into their respective ``uitext/.dir`` directories: +In production, CST files were used in The Sims Online to provide translations It was required that they were prefixed with numbers enclosed in underscores, known as the ID. The IDs were used to locate the right file without knowing it's name. Meanwhile, each translation was split into their respective ``uitext/.dir`` directories: - ``uitext/english.dir/_154_miscstrings.cst`` -- ``uitext/swedish.dir/_154_miscstrings.cst``[^1] +- ``uitext/swedish.dir/_154_miscstrings.cst`` Starting with 1.1, the UIText class provides methods that directorly map to these directories relative to the application's. ```csharp -#r "nuget:Sixam.CST,1.1" +#r "nuget:Sixam.CST,1.1.100" using Sixam.CST; var english = new UIText(); // UIText assumes English var swedish = new UIText("swedish"); -var engExample = english.GetText(101, 1); // english.dir/_101_example.cst -var sweExample = swedish.GetText(101, 1); // swedish.dir/_101_example.cst +var engExample = english.GetText(152, 1); // english.dir/_154_miscstrings.cst +var sweExample = swedish.GetText(152, 1); // swedish.dir/_154_miscstrings.cst Console.WriteLine(engExample); Console.WriteLine(sweExample); ``` -Note that GetText() remains unchanged because the translation is expected to be same id and key, despite being in a different directory. +Note that the IDs and keys in both ``GetText()`` examples remains the same. This is because it was historically assumed that the English and translations files were identical. However, the original never saw any new languages added. ### Changing base directories @@ -57,5 +53,3 @@ var example = new UIText() BasePath = new[] { "gamedata", "uitext" }, }; ``` - -[^1]: TSO only supported English. \ No newline at end of file