From 433e6a2727b4093bf9b825669495450284f093cf Mon Sep 17 00:00:00 2001 From: stubbfel Date: Sun, 26 Feb 2023 19:03:30 +0100 Subject: [PATCH] add draft --- .devcontainer/devcontainer.json | 27 + .gitignore | 484 ++++++++++++++++++ ResourceString.Net.Contract/Class1.cs | 233 +++++++++ .../ResourceString.Net.Contract.csproj | 9 + .../LogicResourcesTests.cs | 68 +++ .../ResourceString.Net.Logic.Tests.csproj | 25 + .../ResxFileTests.cs | 175 +++++++ ResourceString.Net.Logic.Tests/Usings.cs | 4 + .../DomainObjects/Resx/File.cs | 16 + .../DomainObjects/Resx/Resource.cs | 16 + .../Factories/CodeSnippetFactory.cs | 80 +++ ResourceString.Net.Logic/IsExternalInit.cs | 3 + .../Parsers/Resx/Parser.cs | 38 ++ .../Properties/Resources.cs | 67 +++ .../Properties/Resources.resx | 54 ++ .../ResourceString.Net.Logic.csproj | 23 + ResourceString.Net.Logic/Usings.cs | 4 + 17 files changed, 1326 insertions(+) create mode 100644 .devcontainer/devcontainer.json create mode 100644 .gitignore create mode 100644 ResourceString.Net.Contract/Class1.cs create mode 100644 ResourceString.Net.Contract/ResourceString.Net.Contract.csproj create mode 100644 ResourceString.Net.Logic.Tests/LogicResourcesTests.cs create mode 100644 ResourceString.Net.Logic.Tests/ResourceString.Net.Logic.Tests.csproj create mode 100644 ResourceString.Net.Logic.Tests/ResxFileTests.cs create mode 100644 ResourceString.Net.Logic.Tests/Usings.cs create mode 100644 ResourceString.Net.Logic/DomainObjects/Resx/File.cs create mode 100644 ResourceString.Net.Logic/DomainObjects/Resx/Resource.cs create mode 100644 ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs create mode 100644 ResourceString.Net.Logic/IsExternalInit.cs create mode 100644 ResourceString.Net.Logic/Parsers/Resx/Parser.cs create mode 100644 ResourceString.Net.Logic/Properties/Resources.cs create mode 100644 ResourceString.Net.Logic/Properties/Resources.resx create mode 100644 ResourceString.Net.Logic/ResourceString.Net.Logic.csproj create mode 100644 ResourceString.Net.Logic/Usings.cs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000..b2fbe3a --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,27 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the +// README at: https://github.com/devcontainers/templates/tree/main/src/dotnet +{ + "name": "C# (.NET)", + // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile + "image": "mcr.microsoft.com/devcontainers/dotnet:0-6.0" + + // Features to add to the dev container. More info: https://containers.dev/features. + // "features": {}, + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [5000, 5001], + // "portsAttributes": { + // "5001": { + // "protocol": "https" + // } + // } + + // Use 'postCreateCommand' to run commands after the container is created. + // "postCreateCommand": "dotnet restore", + + // Configure tool-specific properties. + // "customizations": {}, + + // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. + // "remoteUser": "root" +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..84a63ca --- /dev/null +++ b/.gitignore @@ -0,0 +1,484 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUnit +*.VisualState.xml +TestResult.xml +nunit-*.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# Benchmark Results +BenchmarkDotNet.Artifacts/ + +# .NET +project.lock.json +project.fragment.lock.json +artifacts/ + +# Tye +.tye/ + +# ASP.NET Scaffolding +ScaffoldingReadMe.txt + +# StyleCop +StyleCopReport.xml + +# Files built by Visual Studio +*_i.c +*_p.c +*_h.h +*.ilk +*.meta +*.obj +*.iobj +*.pch +*.pdb +*.ipdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*_wpftmp.csproj +*.log +*.tlog +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# Visual Studio Trace Files +*.e2e + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# AxoCover is a Code Coverage Tool +.axoCover/* +!.axoCover/settings.json + +# Coverlet is a free, cross platform Code Coverage Tool +coverage*.json +coverage*.xml +coverage*.info + +# Visual Studio code coverage results +*.coverage +*.coveragexml + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# Note: Comment the next line if you want to checkin your web deploy settings, +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt +*.appx +*.appxbundle +*.appxupload + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!?*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +orleans.codegen.cs + +# Including strong name files can present a security risk +# (https://github.com/github/gitignore/pull/2483#issue-259490424) +#*.snk + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm +ServiceFabricBackup/ +*.rptproj.bak + +# SQL Server files +*.mdf +*.ldf +*.ndf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings +*.rptproj.rsuser +*- [Bb]ackup.rdl +*- [Bb]ackup ([0-9]).rdl +*- [Bb]ackup ([0-9][0-9]).rdl + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat +node_modules/ + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio 6 auto-generated workspace file (contains which files were open etc.) +*.vbw + +# Visual Studio 6 auto-generated project file (contains which files were open etc.) +*.vbp + +# Visual Studio 6 workspace and project file (working project files containing files to include in project) +*.dsw +*.dsp + +# Visual Studio 6 technical files +*.ncb +*.aps + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# CodeRush personal settings +.cr/personal + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc + +# Cake - Uncomment if you are using it +# tools/** +# !tools/packages.config + +# Tabs Studio +*.tss + +# Telerik's JustMock configuration file +*.jmconfig + +# BizTalk build output +*.btp.cs +*.btm.cs +*.odx.cs +*.xsd.cs + +# OpenCover UI analysis results +OpenCover/ + +# Azure Stream Analytics local run output +ASALocalRun/ + +# MSBuild Binary and Structured Log +*.binlog + +# NVidia Nsight GPU debugger configuration file +*.nvuser + +# MFractors (Xamarin productivity tool) working folder +.mfractor/ + +# Local History for Visual Studio +.localhistory/ + +# Visual Studio History (VSHistory) files +.vshistory/ + +# BeatPulse healthcheck temp database +healthchecksdb + +# Backup folder for Package Reference Convert tool in Visual Studio 2017 +MigrationBackup/ + +# Ionide (cross platform F# VS Code tools) working folder +.ionide/ + +# Fody - auto-generated XML schema +FodyWeavers.xsd + +# VS Code files for those working on multiple tools +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +# Local History for Visual Studio Code +.history/ + +# Windows Installer files from build outputs +*.cab +*.msi +*.msix +*.msm +*.msp + +# JetBrains Rider +*.sln.iml + +## +## Visual studio for Mac +## + + +# globs +Makefile.in +*.userprefs +*.usertasks +config.make +config.status +aclocal.m4 +install-sh +autom4te.cache/ +*.tar.gz +tarballs/ +test-results/ + +# Mac bundle stuff +*.dmg +*.app + +# content below from: https://github.com/github/gitignore/blob/master/Global/macOS.gitignore +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +# content below from: https://github.com/github/gitignore/blob/master/Global/Windows.gitignore +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +app/ +example/ +testapp/ +testgen/ +tmp/ \ No newline at end of file diff --git a/ResourceString.Net.Contract/Class1.cs b/ResourceString.Net.Contract/Class1.cs new file mode 100644 index 0000000..5dfb429 --- /dev/null +++ b/ResourceString.Net.Contract/Class1.cs @@ -0,0 +1,233 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Resources; +using System.Threading; + +namespace ResourceString.Net.Contract +{ + public interface IResourceString + { + + string Value { get; } + + string GetValue(CultureInfo cultureInfo); + } + + public class LiteralString : IResourceString + { + private Lazy m_Value; + + public string Value => m_Value.Value; + + public static IResourceString Empty {get;} = Factory(string.Empty); + public static IResourceString Factory(string source) + { + return new LiteralString(() => source); + } + + public LiteralString(Func factory) + { + m_Value = new Lazy( + () => factory?.Invoke() ?? string.Empty, + LazyThreadSafetyMode.PublicationOnly + ); + } + + public string GetValue(CultureInfo _) => Value; + } + + public class ResourceManagerString : IResourceString + { + private readonly CultureInfo m_Culture; + + public string Id { get; } + + public ResourceManager Manager { get; } + + public string Value => GetValue(m_Culture); + + public ResourceManagerString( + string id, + ResourceManager manager, + CultureInfo cultureInfo) + { + Id = id ?? string.Empty; + Manager = manager ?? throw new ArgumentNullException(nameof(manager)); + m_Culture = cultureInfo ?? CultureInfo.CurrentCulture; + } + + public string GetValue(CultureInfo cultureInfo) + { + return Manager.GetString(Id, cultureInfo); + } + } + + public class FormattableResourceString : IResourceString + { + public IResourceString Format { get; } + + public IEnumerable Parameters { get; } + + public string Value => GetValue(CultureInfo.CurrentCulture); + + public FormattableResourceString( + IResourceString format, + params IResourceString[] parameters) + { + Format = format ?? new JoinedResourceString( + LiteralString.Factory(","), + parameters.Select(((p, idx) => LiteralString.Factory("{" + idx + "}"))).ToArray() + ); + + Parameters = parameters; + } + + public string GetValue(CultureInfo cultureInfo) + { + var format = Format.GetValue(cultureInfo); + var parameterStrings = Parameters.Select(p => p.GetValue(cultureInfo)); + return string.Format(format, parameterStrings.ToArray()); + } + } + + public class JoinedResourceString : IResourceString + { + public IResourceString Separator { get; } + + public IEnumerable Elements { get; } + + public string Value => GetValue(CultureInfo.CurrentCulture); + + public JoinedResourceString( + IResourceString separator, + params IResourceString[] elements) + { + Separator = separator ?? new LiteralString( + () => "," + ); + + Elements = elements; + } + + public string GetValue(CultureInfo cultureInfo) + { + var separator = Separator.GetValue(cultureInfo); + var elementsStrings = Elements.Select(p => p.GetValue(cultureInfo)); + return string.Join(separator, elementsStrings); + } + } + + internal static class ResourceStrings + { + private static readonly ResourceManager ResourceManager = new ResourceManager(typeof(int)); + + private static readonly Lazy LazyEntry = new Lazy( + () => new ResourceManagerString("EntryId", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static ResourceManagerString Entry => LazyEntry.Value; + + public static class FormattedEntry + { + private static readonly Lazy Value = new Lazy( + () => new ResourceManagerString("FormatEntryId", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static string Id => Value.Value.Id; + + public static ResourceManager Manager => Value.Value.Manager; + + public static IResourceString Format => Value.Value; + + public static IResourceString From(IResourceString p0, IResourceString p1) => new FormattableResourceString( + Format, + p0, + p1 + ); + + + static IEnumerable test(IResourceReader r) + { + var enumerator = r.GetEnumerator(); + + var dict = enumerator.ToDictionary(); + + return dict.Select(e =>new LiteralString(() => CreateString(e))); + + string CreateString(KeyValuePair entry) + { + var key = entry.Key; + var keyName = key.Replace(" ", "").Trim(); + return @$" + private static readonly Lazy Lazy{keyName} = new Lazy( + () => new ResourceString(""{key}"", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static ResourceString {keyName} => Lazy{keyName}.Value; + "; + } + } + + public static class Hello + { + public static IResourceString Format { get; } = LiteralString.Factory("{0}, {1} !!!"); + + public static IResourceString Value { get; } = LiteralString.Factory("Hello"); + + public static IResourceString From(IResourceString name) => new FormattableResourceString( + Format, + Value, + name + ); + } + + public static class HelloWorld + { + public static IResourceString Value { get; } = Hello.From(World); + + public static IResourceString From(IResourceString name) => new FormattableResourceString( + Hello.Format, + Value, + name + ); + } + + public static IResourceString World { get; } = LiteralString.Factory("World"); + + } + } + + public static class Extensions + { + public static IDictionary ToDictionary(this IDictionaryEnumerator source) + { + return source.Collect().ToDictionary(k => k.Key, v => v.Value); + } + + public static IEnumerable> Collect(this IDictionaryEnumerator source) + { + if (source is null) + { + yield break; + } + + while (source.MoveNext()) + { + if (source.Key is TKey key && source.Value is TValue value) + { + yield return new KeyValuePair( + key, + value + ); + } + } + } + } +} + diff --git a/ResourceString.Net.Contract/ResourceString.Net.Contract.csproj b/ResourceString.Net.Contract/ResourceString.Net.Contract.csproj new file mode 100644 index 0000000..ddd5277 --- /dev/null +++ b/ResourceString.Net.Contract/ResourceString.Net.Contract.csproj @@ -0,0 +1,9 @@ + + + + netstandard2.0 + latest + enable + + + diff --git a/ResourceString.Net.Logic.Tests/LogicResourcesTests.cs b/ResourceString.Net.Logic.Tests/LogicResourcesTests.cs new file mode 100644 index 0000000..6fff334 --- /dev/null +++ b/ResourceString.Net.Logic.Tests/LogicResourcesTests.cs @@ -0,0 +1,68 @@ +using ResourceString.Net.Logic.Factories; + +namespace ResourceString.Net.Logic.Tests; + +[TestClass] +public class LogicResourcesTests +{ + + [TestMethod] + public void Test_Get_ResourceStringMembers() + { + var rm = Properties.Resources.ResourceManager; + Assert.AreEqual( + new FormattableResourceString( + Properties.Resources.ResourceStringMembers, + LiteralString.Factory("ResourceStringMembers"), + LiteralString.Factory("ResourceManager") + ).Value, + string.Format( + rm.GetString("ResourceStringMembers") ?? string.Empty, + "ResourceStringMembers", + "ResourceManager" + ) + ); + } + + [TestMethod] + public void Test_Get_ResourceManagerMemberTemplate() + { + var rm = Properties.Resources.ResourceManager; + Assert.AreEqual( + CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet( + "Resources" + ).Value, + string.Format( + rm.GetString("ResourceManagerMemberTemplate") ?? string.Empty, + "Resources" + ) + ); + } + + [TestMethod] + public void Test_Get_ResourcesClassTemplate() + { + var rm = Properties.Resources.ResourceManager; + + var rmSnippet = CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet( + "Resources" + ); + + Assert.AreEqual( + CodeSnippetFactory.CreateResourceClassCodeSnippet( + "ResourceString.Net.Logic.Properties", + "Resources", + rmSnippet, + Enumerable.Empty() + ).Value, + string.Format( + rm.GetString("ResourcesClassTemplate") ?? string.Empty, + "ResourceString.Net.Logic.Properties", + "Resources", + rmSnippet.Value, + string.Empty + ) + ); + } +} + diff --git a/ResourceString.Net.Logic.Tests/ResourceString.Net.Logic.Tests.csproj b/ResourceString.Net.Logic.Tests/ResourceString.Net.Logic.Tests.csproj new file mode 100644 index 0000000..63a4e8f --- /dev/null +++ b/ResourceString.Net.Logic.Tests/ResourceString.Net.Logic.Tests.csproj @@ -0,0 +1,25 @@ + + + + net7.0 + enable + enable + latest + false + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + diff --git a/ResourceString.Net.Logic.Tests/ResxFileTests.cs b/ResourceString.Net.Logic.Tests/ResxFileTests.cs new file mode 100644 index 0000000..1e18a5b --- /dev/null +++ b/ResourceString.Net.Logic.Tests/ResxFileTests.cs @@ -0,0 +1,175 @@ +using ResourceString.Net.Logic.DomainObjects.Resx; +using ResourceString.Net.Logic.Factories; +using ResourceString.Net.Logic.Parsers.Resx; + +namespace ResourceString.Net.Logic.Tests; + +[TestClass] +public class ResxFileTests +{ + private readonly string validXml = @$" + + + Value1 + + + Value2 + +"; + +private const string expectedClass = @" +using ResourceString.Net.Contract; +using System; +using System.Globalization; +using System.Resources; +using System.Threading; + +namespace TestNameSpace +{ + internal static class TestResourceClass + { + + #region ResourceManager + + private static readonly Type _Type = typeof(ResourceManager); + + private static readonly Lazy _ResourceManager = new Lazy( + () => new ResourceManager(_Type.FullName, _Type.Assembly), + LazyThreadSafetyMode.PublicationOnly + ); + + public static ResourceManager ResourceManager => _ResourceManager.Value; + + #endregion // ResourceManager + + + #region Test1 + + private static readonly Lazy LazyTest1 = new Lazy( + () => new ResourceManagerString(""Test1"", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static IResourceString Test1 => LazyTest1.Value; + + #endregion // Test1 + + #region Test2 + + private static readonly Lazy LazyTest2 = new Lazy( + () => new ResourceManagerString(""Test2"", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static IResourceString Test2 => LazyTest2.Value; + + #endregion // Test2 + + } +}"; + + private readonly string emptyXml = @""; + + private readonly string invalidXml = @""; + + [TestMethod] + public void Test_TryParse_ValidXml() + { + // Arrange + var expectedData = new[] { + ("Test1", "Value1", string.Empty), + ("Test2", "Value2", typeof(int).FullName) + }; + + // Act + var result = Parser.TryParse(validXml).Match( + Some: v => v.Resources, + None: () => throw new InvalidOperationException() + ); + + // Assert + CollectionAssert.AreEqual( + expectedData, + result.Select(i => (i.Name, i.Value, i.Type.IfNone(string.Empty))).ToList() + ); + } + + [TestMethod] + public void Test_TryParse_EmptyXml() + { + // Arrange + var expectedData = Enumerable.Empty(); + + // Act + var isSome = Parser.TryParse(emptyXml).Match( + Some: _ => true, + None: () => false + ); + + // Assert + Assert.IsFalse(isSome); + } + + [TestMethod] + public void Test_TryParse_InValidXml() + { + // Arrange + var expectedData = Enumerable.Empty(); + + // Act + var isSome = Parser.TryParse(invalidXml).Match( + Some: _ => true, + None: () => false + ); + + // Assert + Assert.IsFalse(isSome); + } + + [TestMethod] + public void Test_ToMemberString() + { + // Act + var result = Parser.TryParse(validXml).Match( + Some: v => CodeSnippetFactory.CreateMemberCodeSnippets(v.Resources), + None: () => throw new InvalidOperationException() + ); + + // Assert + Assert.AreEqual(2, result.Count()); + + Assert.AreEqual( + "#region Test1", + result.Select(i => i.Value.Substring(0, 25).Trim()).ToList()[0] + ); + + Assert.AreEqual( + "#region Test2", + result.Select(i => i.Value.Substring(0, 25).Trim()).ToList()[1] + ); + + } + + [TestMethod] + public void Test_ToClassString() + { + // Act + var result = Parser.TryParse(validXml).Match( + Some: v => CodeSnippetFactory.CreateResourceClassCodeSnippet( + "TestNameSpace", + "TestResourceClass", + CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet("ResourceManager"), + v.Resources + ), + None: () => throw new InvalidOperationException() + ); + + // Assert + Assert.AreEqual( + expectedClass.Trim(), + result.Value.Trim() + ); + } +} + + diff --git a/ResourceString.Net.Logic.Tests/Usings.cs b/ResourceString.Net.Logic.Tests/Usings.cs new file mode 100644 index 0000000..9fc20fb --- /dev/null +++ b/ResourceString.Net.Logic.Tests/Usings.cs @@ -0,0 +1,4 @@ +global using LanguageExt; +global using Microsoft.VisualStudio.TestTools.UnitTesting; +global using ResourceString.Net.Contract; +global using ResourceString.Net.Logic; diff --git a/ResourceString.Net.Logic/DomainObjects/Resx/File.cs b/ResourceString.Net.Logic/DomainObjects/Resx/File.cs new file mode 100644 index 0000000..c724563 --- /dev/null +++ b/ResourceString.Net.Logic/DomainObjects/Resx/File.cs @@ -0,0 +1,16 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ResourceString.Net.Logic.DomainObjects.Resx; + +internal record File +{ + public File(IEnumerable resources) + { + Resources = new Seq( + resources?.Where(o => o != null) ?? Enumerable.Empty() + ); + } + + public IEnumerable Resources { get; } +} diff --git a/ResourceString.Net.Logic/DomainObjects/Resx/Resource.cs b/ResourceString.Net.Logic/DomainObjects/Resx/Resource.cs new file mode 100644 index 0000000..d263b66 --- /dev/null +++ b/ResourceString.Net.Logic/DomainObjects/Resx/Resource.cs @@ -0,0 +1,16 @@ +namespace ResourceString.Net.Logic.DomainObjects.Resx; + +internal record Resource +{ + public Resource(string name, string value) + { + Name = name ?? string.Empty; + Value = value ?? string.Empty; + } + + public string Name { get;} + + public Option Type { get; init;} + + public string Value { get; } +} diff --git a/ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs b/ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs new file mode 100644 index 0000000..c84c1ee --- /dev/null +++ b/ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs @@ -0,0 +1,80 @@ +using ResourceString.Net.Logic.DomainObjects.Resx; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace ResourceString.Net.Logic.Factories; + +internal static class CodeSnippetFactory +{ + public static IResourceString CreateResourceMangerMemberCodeSnippet(string typeName) + { + var formatString = Properties.Resources.ResourceManagerMemberTemplate; + return new FormattableResourceString( + formatString, + LiteralString.Factory(typeName) + ); + } + + + public static IEnumerable CreateMemberCodeSnippets(IEnumerable resources) + { + return CreateMemberCodeSnippets( + resources, + Properties.Resources.DefaultPropertyName_ResourceManager + ); + } + + public static IEnumerable CreateMemberCodeSnippets( + IEnumerable resources, + IResourceString resourceManagerName + ) + { + if (resources is null) + { + return Enumerable.Empty(); + } + + var formatString = Properties.Resources.ResourceStringMembers; + return resources.Select(r => new FormattableResourceString( + formatString, + LiteralString.Factory(r.Name), + resourceManagerName + )); + } + + public static IResourceString CreateResourceClassCodeSnippet( + string namespaceString, + string resourceClassName, + IResourceString resourceManagerSnippet, + IEnumerable memberSnippets + ) + { + var formatString = Properties.Resources.ResourcesClassTemplate; + return new FormattableResourceString( + formatString, + LiteralString.Factory(namespaceString), + LiteralString.Factory(resourceClassName), + resourceManagerSnippet, + new JoinedResourceString( + LiteralString.Empty, + memberSnippets?.ToArray() ?? Array.Empty() + ) + ); + } + + public static IResourceString CreateResourceClassCodeSnippet( + string namespaceString, + string resourceClassName, + IResourceString resourceManagerSnippet, + IEnumerable resources + ) + { + return CreateResourceClassCodeSnippet( + namespaceString, + resourceClassName, + resourceManagerSnippet, + CreateMemberCodeSnippets(resources) + ); + } +} \ No newline at end of file diff --git a/ResourceString.Net.Logic/IsExternalInit.cs b/ResourceString.Net.Logic/IsExternalInit.cs new file mode 100644 index 0000000..ed1a03e --- /dev/null +++ b/ResourceString.Net.Logic/IsExternalInit.cs @@ -0,0 +1,3 @@ +namespace System.Runtime.CompilerServices; + +internal static class IsExternalInit { } diff --git a/ResourceString.Net.Logic/Parsers/Resx/Parser.cs b/ResourceString.Net.Logic/Parsers/Resx/Parser.cs new file mode 100644 index 0000000..727e44b --- /dev/null +++ b/ResourceString.Net.Logic/Parsers/Resx/Parser.cs @@ -0,0 +1,38 @@ +using ResourceString.Net.Logic.DomainObjects.Resx; +using System.Linq; +using System.Xml; + +namespace ResourceString.Net.Logic.Parsers.Resx; + +internal static class Parser +{ + public static Option TryParse(string xmlString) + { + var doc = new XmlDocument(); + + try + { + doc.LoadXml(xmlString); + } + catch + { + return Option.None; + } + + var resources = doc.SelectNodes("descendant::data").OfType().Select((i, _) => + { + var name = i.GetAttribute("name"); + var type = i.GetAttribute("type"); + var value = i.SelectSingleNode("descendant::value")?.InnerXml; + + return new Resource(name, value ?? string.Empty) + { + Type = type + }; + }).ToArray(); + + return resources.Any() + ? Option.Some(new File(resources)) + : Option.None; + } +} diff --git a/ResourceString.Net.Logic/Properties/Resources.cs b/ResourceString.Net.Logic/Properties/Resources.cs new file mode 100644 index 0000000..f383f7f --- /dev/null +++ b/ResourceString.Net.Logic/Properties/Resources.cs @@ -0,0 +1,67 @@ +using System; +using System.Globalization; +using System.Resources; +using System.Threading; + +namespace ResourceString.Net.Logic.Properties +{ + internal static class Resources + { + #region ResourceManager + + private static readonly Type _Type = typeof(Resources); + + private static readonly Lazy _ResourceManager = new Lazy( + () => new ResourceManager(_Type.FullName, _Type.Assembly), + LazyThreadSafetyMode.PublicationOnly + ); + + public static ResourceManager ResourceManager => _ResourceManager.Value; + + #endregion // ResourceManager + + #region ResourceStringMembers + + private static readonly Lazy LazyResourceStringMembers = new Lazy( + () => new ResourceManagerString("ResourceStringMembers", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static IResourceString ResourceStringMembers => LazyResourceStringMembers.Value; + + #endregion // ResourceStringMembers + + #region ResourceManagerMemberTemplate + + private static readonly Lazy LazyResourceManagerMemberTemplate = new Lazy( + () => new ResourceManagerString("ResourceManagerMemberTemplate", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static IResourceString ResourceManagerMemberTemplate => LazyResourceManagerMemberTemplate.Value; + + #endregion // ResourceManagerMemberTemplate + + #region ResourcesClassTemplate + + private static readonly Lazy LazyResourcesClassTemplate = new Lazy( + () => new ResourceManagerString("ResourcesClassTemplate", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static IResourceString ResourcesClassTemplate => LazyResourcesClassTemplate.Value; + + #endregion // ResourcesClassTemplate + + #region DefaultPropertyName_ResourceManager + + private static readonly Lazy LazyDefaultPropertyName_ResourceManager = new Lazy( + () => new ResourceManagerString("DefaultPropertyName_ResourceManager", ResourceManager, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static IResourceString DefaultPropertyName_ResourceManager => LazyDefaultPropertyName_ResourceManager.Value; + + #endregion // DefaultPropertyName_ResourceManager + } +} diff --git a/ResourceString.Net.Logic/Properties/Resources.resx b/ResourceString.Net.Logic/Properties/Resources.resx new file mode 100644 index 0000000..3f7df41 --- /dev/null +++ b/ResourceString.Net.Logic/Properties/Resources.resx @@ -0,0 +1,54 @@ + + + + + #region {0} + + private static readonly Lazy<IResourceString> Lazy{0} = new Lazy<IResourceString>( + () => new ResourceManagerString("{0}", {1}, CultureInfo.CurrentCulture), + LazyThreadSafetyMode.PublicationOnly + ); + + public static IResourceString {0} => Lazy{0}.Value; + + #endregion // {0} + + + + +using ResourceString.Net.Contract; +using System; +using System.Globalization; +using System.Resources; +using System.Threading; + +namespace {0} +{{ + internal static class {1} + {{ +{2} +{3} + }} +}} + + + + + #region ResourceManager + + private static readonly Type _Type = typeof({0}); + + private static readonly Lazy<ResourceManager> _ResourceManager = new Lazy<ResourceManager>( + () => new ResourceManager(_Type.FullName, _Type.Assembly), + LazyThreadSafetyMode.PublicationOnly + ); + + public static ResourceManager ResourceManager => _ResourceManager.Value; + + #endregion // ResourceManager + + + + ResourceManager + + diff --git a/ResourceString.Net.Logic/ResourceString.Net.Logic.csproj b/ResourceString.Net.Logic/ResourceString.Net.Logic.csproj new file mode 100644 index 0000000..70b1a06 --- /dev/null +++ b/ResourceString.Net.Logic/ResourceString.Net.Logic.csproj @@ -0,0 +1,23 @@ + + + + netstandard2.0 + latest + enable + + + + + + + + + + + + + + <_Parameter1>$(MSBuildProjectName).Tests + + + diff --git a/ResourceString.Net.Logic/Usings.cs b/ResourceString.Net.Logic/Usings.cs new file mode 100644 index 0000000..dc739e8 --- /dev/null +++ b/ResourceString.Net.Logic/Usings.cs @@ -0,0 +1,4 @@ +global using LanguageExt; + +global using ResourceString.Net.Contract; +