diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json
new file mode 100644
index 0000000..bed90d0
--- /dev/null
+++ b/.devcontainer/devcontainer.json
@@ -0,0 +1,36 @@
+// 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": {
+ "ghcr.io/devcontainers/features/nix:1": {}
+ },
+ "customizations": {
+ "vscode": {
+ "extensions": [
+ "formulahendry.dotnet-test-explorer",
+ "jnoortheen.nix-ide",
+ "eamodio.gitlens"
+ ]
+ }
+ },
+
+ // 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": "nix-env -i nixpkgs-fmt"
+
+ // 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..70f8ac5
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,478 @@
+## 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
+result
\ No newline at end of file
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..25a5e11
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,40 @@
+{
+ "version": "0.2.0",
+ "configurations": [
+
+ {
+ // Use IntelliSense to find out which attributes exist for C# debugging
+ // Use hover for the description of the existing attributes
+ // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md
+ "name": "Launch Example App",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ // If you have changed target frameworks, make sure to update the program path.
+ "program": "${workspaceFolder}/example/Example.App/bin/Debug/net6.0/Example.App.dll",
+ "args": [],
+ "cwd": "${workspaceFolder}/example/Example.App",
+ // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console
+ "console": "internalConsole",
+ "stopAtEntry": false
+ },
+ {
+ "name": "Update Resources.cs",
+ "type": "coreclr",
+ "request": "launch",
+ "preLaunchTask": "build",
+ "program": "${workspaceFolder}/ResourceString.Net.App.Console/bin/Debug/net6.0/ResourceString.Net.App.Console.dll",
+ "args": [
+ "${workspaceFolder}/ResourceString.Net.Logic/Properties/Resources.resx"
+ ],
+ "cwd": "${workspaceFolder}/ResourceString.Net.App.Console",
+ "console": "integratedTerminal",
+ "stopAtEntry": false
+ },
+ {
+ "name": ".NET Core Attach",
+ "type": "coreclr",
+ "request": "attach"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..40df522
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,3 @@
+{
+ "dotnet-test-explorer.testProjectPath": "**/*Tests.csproj"
+}
\ No newline at end of file
diff --git a/.vscode/tasks.json b/.vscode/tasks.json
new file mode 100644
index 0000000..c09ae0f
--- /dev/null
+++ b/.vscode/tasks.json
@@ -0,0 +1,41 @@
+{
+ "version": "2.0.0",
+ "tasks": [
+ {
+ "label": "build",
+ "command": "nix",
+ "type": "process",
+ "args": [
+ "--experimental-features",
+ "nix-command flakes",
+ "run",
+ ".#devTasks.buildAllAssemblies"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "publish",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "publish",
+ "${workspaceFolder}/example/Example.App/Example.App.csproj",
+ "/property:GenerateFullPaths=true",
+ "/consoleloggerparameters:NoSummary"
+ ],
+ "problemMatcher": "$msCompile"
+ },
+ {
+ "label": "watch",
+ "command": "dotnet",
+ "type": "process",
+ "args": [
+ "watch",
+ "run",
+ "--project",
+ "${workspaceFolder}/example/Example.App/Example.App.csproj"
+ ],
+ "problemMatcher": "$msCompile"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..a56c50e
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+MIT License
+
+Copyright (c) 2023 stubbfel
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Readme.md b/Readme.md
index e69de29..06e0613 100644
--- a/Readme.md
+++ b/Readme.md
@@ -0,0 +1,240 @@
+# ResourceString.Net
+
+## What is ResourceString.Net?
+
+ResourceString.Net is a powerful .NET library that allows you to work with string resources in a type-safe manner.
+It leverages the `resx` file in your project and utilizes a [c# source code generator](https://learn.microsoft.com/en-us/dotnet/csharp/roslyn-sdk/source-generators-overview) to create a comprehensive API.
+With ResourceString.Net, you can handle resource strings as "multi-language strings" (see [The ResourceString-Classes](#the-resourcestring-classes)) instead of built-in strings.
+This provides the ability to switch languages during runtime without the need to rerun string factory methods.
+Additionally, ResourceString.Net ensures that formatted strings have methods with the correct number of expected parameters.
+
+## Installation and Setup Instructions
+
+To incorporate ResourceString.Net into your .NET application, follow these simple steps:
+
+1. Install the NuGet package by executing the following command in the NuGet package manager or the dotnet CLI:
+
+```sh
+dotnet add package ResourceString.Net
+```
+
+2. Once the package is installed, you can start using the APIs in your application.
+
+## Getting Started
+
+To quickly get started with ResourceString.Net, follow the steps below:
+
+1. Run the following script to create a new project and a resource file:
+
+```sh
+dotnet new console --name MyTestConsoleApp
+cd MyTestConsoleApp
+
+dotnet add package "System.Resources.Extensions"
+dotnet add package "ResourceString.Net"
+
+echo "
+
+
+ Hello {0}
+ 0 = name
+
+
+ World
+
+
+" > Resources.resx
+
+echo "var message = MyTestConsoleApp.Resources.Greetings.From(
+ MyTestConsoleApp.Resources.World
+);
+
+Console.WriteLine(message.Value);
+" > Program.cs
+```
+
+2. Add the following `PropertyGroup` to the `MyTestConsoleApp.csproj` file to enable ResourceString.Net to handle the project's resource file:
+
+```xml
+
+ $(AdditionalFileItemNames);EmbeddedResource
+
+```
+
+3. Run the project using the following command:
+
+```sh
+dotnet run
+# Expected output: Hello World
+```
+
+During compile time, the ResourceString.Net source code generator will automatically add the following code to the `MyTestConsoleApp.csproj` project:
+
+```cs
+using ResourceString.Net.Contract;
+using System;
+using System.Globalization;
+using System.Resources;
+using System.Threading;
+
+namespace MyTestConsoleApp
+{
+ internal static class Resources
+ {
+
+ #region ResourceManager
+
+ private static readonly Type _Type = typeof(Resources);
+
+ private static readonly Lazy _ResourceManager = new Lazy(
+ () => new ResourceManager(_Type.FullName ?? string.Empty, _Type.Assembly),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static ResourceManager ResourceManager => _ResourceManager.Value;
+
+ #endregion // ResourceManager
+
+
+ internal static class Greetings
+ {
+ private static readonly Lazy LazyFormat = new Lazy(
+ () => new ResourceManagerString("Greetings", ResourceManager, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString Format => LazyFormat.Value;
+
+ public static IResourceString From(IResourceString name) => new FormattedResourceString(
+ Format,
+ name
+ );
+
+ }
+
+ #region World
+
+ private static readonly Lazy LazyWorld = new Lazy(
+ () => new ResourceManagerString("World", ResourceManager, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString World => LazyWorld.Value;
+
+ #endregion // World
+
+ }
+}
+
+```
+
+## The ResourceString-Classes
+
+### IResourceString
+
+- Interface that defines the contract for resource strings.
+- Contains properties: `Value`, representing the string value, and `GetValue(CultureInfo cultureInfo)`, for retrieving the value for a specific `CultureInfo`.
+
+### FormattedResourceString
+
+- Implements the `IResourceString` interface.
+- Represents a resource string with placeholders for parameters.
+- Allows for dynamic formatting of the string by providing a format and an array of parameters.
+- Formats the string by replacing the placeholders with the parameter values.
+
+### JoinedResourceString
+
+- Implements the `IResourceString` interface.
+- Represents a resource string that joins multiple elements with a separator.
+- Useful for constructing strings that involve concatenating multiple resource strings or literal strings together.
+- Allows customization of the separator between the elements.
+
+### LiteralString
+
+- Implements the `IResourceString` interface.
+- Represents a literal string resource.
+- Provides the actual string value as-is without any formatting or localization.
+- Can be used for static, non-localized strings.
+
+### ResourceManagerString
+
+- Implements the `IResourceString` interface.
+- Represents a resource string retrieved from a `ResourceManager`.
+- Provides access to resource strings stored in `resx` files or other resource sources.
+- Handles retrieving the localized value for the specified `CultureInfo`.
+
+Great! Based on the provided code for the console app `ResourceString.Net.App.Console`, let's enhance the Readme.md file to include instructions on how to use the console app and generate a C# class based on a given resource file.
+
+## Console App: ResourceString.Net.App.Console
+
+The ResourceString.Net project also provide a console app, `ResourceString.Net.App.Console`, which allows you to generate a C# class based on a given resource file. This can be useful for automating the creation of resource classes in your projects without usage of the `source generator`.
+
+### Usage
+
+To use the console app, follow these steps:
+
+1. Build the console app from this source repository
+2. Open a command prompt or terminal.
+3. Navigate to the directory where the `ResourceString.Net.App.Console` executable is located.
+
+#### Syntax
+
+```
+ResourceString.Net.App.Console [namespaceString] [className]
+```
+
+#### Parameters
+
+- ``: Required. The path to the resource file (e.g., `Resources.resx`) that you want to generate the C# class from.
+- `[namespaceString]` (optional): The namespace for the generated C# class. If not provided, the default value is `"Properties"`.
+- `[className]` (optional): The name of the generated C# class. If not provided, the default value is `"Resources"`.
+
+#### Examples
+
+Here are some examples of how to use the console app:
+
+- Generate a C# class named `Resources.cs` in the `"Properties"` namespace based on the `Resources.resx` file:
+
+ ```
+ ResourceString.Net.App.Console Resources.resx
+ ```
+
+- Generate a C# class named `MyResources.cs` in the `"MyNamespace"` namespace based on the `MyResources.resx` file:
+
+ ```
+ ResourceString.Net.App.Console MyResources.resx MyNamespace MyResources
+ ```
+
+### Output
+
+The console app will generate the C# class code based on the provided resource file and output it to the console.
+You can redirect the output to a file if desired.
+
+## Third party packages
+
+| Package | Version |
+| ------------------------------------- | ------- |
+| Fody | 6.7.0 |
+| ILMerge.Fody | 1.24.0 |
+| Microsoft.CodeAnalysis.Analyzers | 3.3.4 |
+| Microsoft.CodeAnalysis.CSharp | 4.3.0 |
+| NETStandard.Library (Auto-referenced) | 2.0.3 |
+| LanguageExt.Core | 4.4.3 |
+| System.Resources.Extensions | 7.0.0 |
+
+## Development Notes
+
+Here are some useful aliases for running Nix commands with experimental features:
+
+- `nixe`: This alias runs the `nix` command with the experimental feature flag `nix-command flakes`.
+- `nulock`: This alias runs the `nixe` command with the argument `run .#devTasks.updateNugetLock`, which updates the NuGet lock file.
+- `fllock`: This alias runs the `nixe` command with the argument `run .#devTasks.updateFlakeLock`, which updates the Flake lock file.
+- `ulock`: This alias combines the `nulock` and `fllock` aliases to update both the NuGet and Flake lock files.
+
+To load the `alias.sh` source file from the current folder, use the following command:
+
+```sh
+source ./alias.sh
+```
+
+By executing the command above, you'll make the aliases available for use in the current terminal session.
diff --git a/ResourceString.Net.App.Console/Program.cs b/ResourceString.Net.App.Console/Program.cs
new file mode 100644
index 0000000..54add19
--- /dev/null
+++ b/ResourceString.Net.App.Console/Program.cs
@@ -0,0 +1,18 @@
+using ResourceString.Net.Logic.Factories;
+using ResourceString.Net.Logic.Parsers.Resx;
+
+var sourceFile = args.First();
+var namespaceString = args.Skip(1).FirstOrDefault() ?? "Properties";
+var className = args.Skip(2).FirstOrDefault() ?? "Resources";
+
+var result = Parser.TryParse(System.IO.File.ReadAllText(sourceFile)).Match(
+ Some: v => CodeSnippetFactory.CreateResourceClassCodeSnippet(
+ namespaceString,
+ className,
+ CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet(className),
+ v.Resources
+ ),
+ None: () => throw new InvalidOperationException()
+);
+
+Console.WriteLine(result.Value.Trim());
diff --git a/ResourceString.Net.App.Console/ResourceString.Net.App.Console.csproj b/ResourceString.Net.App.Console/ResourceString.Net.App.Console.csproj
new file mode 100644
index 0000000..9360a2a
--- /dev/null
+++ b/ResourceString.Net.App.Console/ResourceString.Net.App.Console.csproj
@@ -0,0 +1,16 @@
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ latest
+ true
+
+
+
+
+
+
+
diff --git a/ResourceString.Net.Contract/FormattedResourceString.cs b/ResourceString.Net.Contract/FormattedResourceString.cs
new file mode 100644
index 0000000..1e1f00d
--- /dev/null
+++ b/ResourceString.Net.Contract/FormattedResourceString.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace ResourceString.Net.Contract
+{
+ public class FormattedResourceString : IResourceString
+ {
+ public IResourceString Format { get; }
+
+ public IEnumerable Parameters { get; }
+
+ public string Value => GetValue(CultureInfo.CurrentCulture);
+
+ public FormattedResourceString(
+ 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());
+ }
+ }
+}
+
diff --git a/ResourceString.Net.Contract/IResourceString.cs b/ResourceString.Net.Contract/IResourceString.cs
new file mode 100644
index 0000000..67089e1
--- /dev/null
+++ b/ResourceString.Net.Contract/IResourceString.cs
@@ -0,0 +1,13 @@
+using System.Globalization;
+
+namespace ResourceString.Net.Contract
+{
+ public interface IResourceString
+ {
+
+ string Value { get; }
+
+ string GetValue(CultureInfo cultureInfo);
+ }
+}
+
diff --git a/ResourceString.Net.Contract/JoinedResourceString.cs b/ResourceString.Net.Contract/JoinedResourceString.cs
new file mode 100644
index 0000000..7f6298a
--- /dev/null
+++ b/ResourceString.Net.Contract/JoinedResourceString.cs
@@ -0,0 +1,35 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace ResourceString.Net.Contract
+{
+
+ 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);
+ }
+ }
+}
+
diff --git a/ResourceString.Net.Contract/LiteralString.cs b/ResourceString.Net.Contract/LiteralString.cs
new file mode 100644
index 0000000..2352e47
--- /dev/null
+++ b/ResourceString.Net.Contract/LiteralString.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Globalization;
+using System.Threading;
+
+namespace ResourceString.Net.Contract
+{
+ 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;
+ }
+}
+
diff --git a/ResourceString.Net.Contract/ResourceManagerString.cs b/ResourceString.Net.Contract/ResourceManagerString.cs
new file mode 100644
index 0000000..039b028
--- /dev/null
+++ b/ResourceString.Net.Contract/ResourceManagerString.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Globalization;
+using System.Resources;
+
+namespace ResourceString.Net.Contract
+{
+ 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);
+ }
+ }
+}
+
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..15970e0
--- /dev/null
+++ b/ResourceString.Net.Logic.Tests/LogicResourcesTests.cs
@@ -0,0 +1,106 @@
+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 FormattedResourceString(
+ Properties.Resources.ResourceStringMembers.Format,
+ LiteralString.Factory("ResourceStringMembers"),
+ LiteralString.Factory("ResourceManager")
+ ).Value,
+ string.Format(
+ rm.GetString("ResourceStringMembers") ?? string.Empty,
+ "ResourceStringMembers",
+ "ResourceManager"
+ )
+ );
+ }
+
+ [TestMethod]
+ public void Test_Get_ResourceFormatClassMembers()
+ {
+ var rm = Properties.Resources.ResourceManager;
+ Assert.AreEqual(
+ new FormattedResourceString(
+ Properties.Resources.ResourceFormatClassMembers.Format,
+ LiteralString.Factory("ResourceFormatClassMembers"),
+ LiteralString.Factory("ResourceManager"),
+ LiteralString.Factory("Format")
+ ).Value,
+ string.Format(
+ rm.GetString("ResourceFormatClassMembers") ?? string.Empty,
+ "ResourceFormatClassMembers",
+ "ResourceManager",
+ "Format"
+ )
+ );
+ }
+
+ [TestMethod]
+ public void Test_Get_ResourceFormatClassFromMethod()
+ {
+ var rm = Properties.Resources.ResourceManager;
+ Assert.AreEqual(
+ new FormattedResourceString(
+ Properties.Resources.ResourceFormatClassFromMethod.Format,
+ LiteralString.Factory("IResourceString name"),
+ LiteralString.Factory("name")
+ ).Value,
+ string.Format(
+ rm.GetString("ResourceFormatClassFromMethod") ?? string.Empty,
+ "IResourceString name",
+ "name"
+ )
+ );
+ }
+
+ [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..b3ef7bf
--- /dev/null
+++ b/ResourceString.Net.Logic.Tests/ResourceString.Net.Logic.Tests.csproj
@@ -0,0 +1,25 @@
+
+
+
+ net6.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..0a1ea55
--- /dev/null
+++ b/ResourceString.Net.Logic.Tests/ResxFileTests.cs
@@ -0,0 +1,192 @@
+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
+ This is a greeting message.
+
+
+ {{2}}Value2{{0}}{{{{1}}}}
+ 2 = prefix
+
+
+ 0xDEADBEEF
+
+";
+
+ 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 ?? string.Empty, _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
+
+ internal static class Test2
+ {
+ private static readonly Lazy LazyFormat = new Lazy(
+ () => new ResourceManagerString(""Test2"", ResourceManager, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString Format => LazyFormat.Value;
+
+ public static IResourceString From(IResourceString prefix, IResourceString p1) => new FormattedResourceString(
+ Format,
+ prefix,
+ p1
+ );
+
+ }
+
+ }
+}";
+
+ private readonly string emptyXml = @"";
+
+ private readonly string invalidXml = @"";
+
+ [TestMethod]
+ public void Test_TryParse_ValidXml()
+ {
+ // Arrange
+ var expectedData = new[] {
+ ("Test1", "Value1", string.Empty, "This is a greeting message."),
+ ("Test2", "{2}Value2{0}{{1}}", typeof(string).FullName, "2 = prefix"),
+ ("Test3", "0xDEADBEEF", typeof(byte[]).FullName, string.Empty)
+ };
+
+ // 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),
+ i.Comment.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(
+ "internal static class Test2",
+ result.Select(i => i.Value.Substring(0, 45).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..03c6870
--- /dev/null
+++ b/ResourceString.Net.Logic/DomainObjects/Resx/Resource.cs
@@ -0,0 +1,18 @@
+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 Option Comment { 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..dcdcbf2
--- /dev/null
+++ b/ResourceString.Net.Logic/Factories/CodeSnippetFactory.cs
@@ -0,0 +1,126 @@
+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)
+ {
+ return Properties.Resources.ResourceManagerMemberTemplate.From(
+ 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 stringResourses = resources.Where(r => r.Type.Match(
+ v => typeof(string).IsAssignableFrom(Type.GetType(v.Trim(), false, true)),
+ () => true
+ ));
+
+ return stringResourses.Select(r =>
+ {
+ var openBraces = r.Value
+ .Replace("{{", string.Empty)
+ .Replace("{{", string.Empty)
+ .Split('{')
+ .Skip(1)
+ .ToArray();
+
+ if (openBraces.Any())
+ {
+ var formatNames = new System.Collections.Generic.HashSet(
+ openBraces.Select(b => b.Split('}').First())
+ );
+
+ var resolveParameterNames = r.Comment.Match(
+ c => c.Split(',')
+ .Select(str => str.Split('='))
+ .Where(parts => parts.Count() == 2)
+ .Select(parts => (
+ parts.First().Trim().Trim('{', '}').Split(' ').First(),
+ parts.Last().Trim().Split(' ').First()
+ )).Where(t => uint.TryParse(t.Item1, out var _))
+ .GroupBy(t => t.Item1)
+ .ToDictionary(k => k.Key, v => v.First().Item2),
+ () => new Dictionary()
+ );
+
+ var parameterNames = formatNames.Select(
+ n => resolveParameterNames.TryGetValue(n.Trim(), out var paramName)
+ ? paramName.Substring(0, 1).ToLower() + paramName.Substring(1)
+ : uint.TryParse(n, out var value) ? $"p{value + 1}" : n
+ ).ToArray();
+
+ var from = Properties.Resources.ResourceFormatClassFromMethod.From(
+ new JoinedResourceString(
+ LiteralString.Factory(", "),
+ parameterNames.Select(n => LiteralString.Factory($"{nameof(IResourceString)} {n}")).ToArray()
+ ),
+ new JoinedResourceString(
+ LiteralString.Factory($",{System.Environment.NewLine} "),
+ parameterNames.Select(n => LiteralString.Factory($"{n}")).ToArray()
+ )
+ );
+ return Properties.Resources.ResourceFormatClassMembers.From(
+ LiteralString.Factory(r.Name), resourceManagerName, from
+ );
+ }
+ return Properties.Resources.ResourceStringMembers.From(
+ LiteralString.Factory(r.Name), resourceManagerName
+ );
+ });
+ }
+
+ public static IResourceString CreateResourceClassCodeSnippet(
+ string namespaceString,
+ string resourceClassName,
+ IResourceString resourceManagerSnippet,
+ IEnumerable memberSnippets
+ )
+ {
+ return Properties.Resources.ResourcesClassTemplate.From(
+ 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..5c9df5b
--- /dev/null
+++ b/ResourceString.Net.Logic/Parsers/Resx/Parser.cs
@@ -0,0 +1,44 @@
+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 comment = i.SelectSingleNode("descendant::comment")?.InnerXml ?? string.Empty;
+ var value = i.SelectSingleNode("descendant::value")?.InnerXml ?? string.Empty;
+
+ return new Resource(name, value)
+ {
+ Type = string.IsNullOrWhiteSpace(type)
+ ? Option.None
+ : Option.Some(type),
+ Comment = string.IsNullOrWhiteSpace(comment)
+ ? Option.None
+ : Option.Some(comment)
+ };
+ }).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..a901733
--- /dev/null
+++ b/ResourceString.Net.Logic/Properties/Resources.cs
@@ -0,0 +1,125 @@
+using ResourceString.Net.Contract;
+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 ?? string.Empty, _Type.Assembly),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static ResourceManager ResourceManager => _ResourceManager.Value;
+
+ #endregion // ResourceManager
+
+
+ internal static class ResourceStringMembers
+ {
+ private static readonly Lazy LazyFormat = new Lazy(
+ () => new ResourceManagerString("ResourceStringMembers", ResourceManager, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString Format => LazyFormat.Value;
+
+ public static IResourceString From(IResourceString resourceId, IResourceString resourceManagerPropertyName) => new FormattedResourceString(
+ Format,
+ resourceId,
+ resourceManagerPropertyName
+ );
+
+ }
+
+ internal static class ResourceFormatClassMembers
+ {
+ private static readonly Lazy LazyFormat = new Lazy(
+ () => new ResourceManagerString("ResourceFormatClassMembers", ResourceManager, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString Format => LazyFormat.Value;
+
+ public static IResourceString From(IResourceString resourceId, IResourceString resourceManagerPropertyName, IResourceString fromMethodDefinition) => new FormattedResourceString(
+ Format,
+ resourceId,
+ resourceManagerPropertyName,
+ fromMethodDefinition
+ );
+
+ }
+
+ internal static class ResourceFormatClassFromMethod
+ {
+ private static readonly Lazy LazyFormat = new Lazy(
+ () => new ResourceManagerString("ResourceFormatClassFromMethod", ResourceManager, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString Format => LazyFormat.Value;
+
+ public static IResourceString From(IResourceString fromMethodSignature, IResourceString parameterNames) => new FormattedResourceString(
+ Format,
+ fromMethodSignature,
+ parameterNames
+ );
+
+ }
+
+ internal static class ResourcesClassTemplate
+ {
+ private static readonly Lazy LazyFormat = new Lazy(
+ () => new ResourceManagerString("ResourcesClassTemplate", ResourceManager, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString Format => LazyFormat.Value;
+
+ public static IResourceString From(IResourceString ns, IResourceString className, IResourceString resourceManagerRegion, IResourceString resourceRegions) => new FormattedResourceString(
+ Format,
+ ns,
+ className,
+ resourceManagerRegion,
+ resourceRegions
+ );
+
+ }
+
+ internal static class ResourceManagerMemberTemplate
+ {
+ private static readonly Lazy LazyFormat = new Lazy(
+ () => new ResourceManagerString("ResourceManagerMemberTemplate", ResourceManager, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString Format => LazyFormat.Value;
+
+ public static IResourceString From(IResourceString resourceManagerTypeName) => new FormattedResourceString(
+ Format,
+ resourceManagerTypeName
+ );
+
+ }
+
+ #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..9fd20fe
--- /dev/null
+++ b/ResourceString.Net.Logic/Properties/Resources.resx
@@ -0,0 +1,81 @@
+
+
+
+
+ #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}
+
+ 0 = ResourceId, 1 = ResourceManagerPropertyName
+
+
+
+ internal static class {0}
+ {{
+ private static readonly Lazy<IResourceString> LazyFormat = new Lazy<IResourceString>(
+ () => new ResourceManagerString("{0}", {1}, CultureInfo.CurrentCulture),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static IResourceString Format => LazyFormat.Value;
+ {2}
+ }}
+
+ 0 = ResourceId, 1 = ResourceManagerPropertyName, 2 = FromMethodDefinition
+
+
+
+ public static IResourceString From({0}) => new FormattedResourceString(
+ Format,
+ {1}
+ );
+
+ 0 = FromMethodSignature, 1 = ParameterNames
+
+
+
+using ResourceString.Net.Contract;
+using System;
+using System.Globalization;
+using System.Resources;
+using System.Threading;
+
+namespace {0}
+{{
+ internal static class {1}
+ {{
+{2}
+{3}
+ }}
+}}
+
+ 0 = ns, 1 = ClassName, 2 = ResourceManagerRegion, 3 = ResourceRegions
+
+
+
+ #region ResourceManager
+
+ private static readonly Type _Type = typeof({0});
+
+ private static readonly Lazy<ResourceManager> _ResourceManager = new Lazy<ResourceManager>(
+ () => new ResourceManager(_Type.FullName ?? string.Empty, _Type.Assembly),
+ LazyThreadSafetyMode.PublicationOnly
+ );
+
+ public static ResourceManager ResourceManager => _ResourceManager.Value;
+
+ #endregion // ResourceManager
+
+ 0 = ResourceManagerTypeName
+
+
+ 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..1746496
--- /dev/null
+++ b/ResourceString.Net.Logic/ResourceString.Net.Logic.csproj
@@ -0,0 +1,29 @@
+
+
+
+ netstandard2.0
+ latest
+ enable
+
+
+
+
+
+
+
+
+
+
+
+
+
+ <_Parameter1>$(MSBuildProjectName).Tests
+
+
+ <_Parameter1>ResourceString.Net.App.Console
+
+
+ <_Parameter1>ResourceString.Net
+
+
+
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;
+
diff --git a/ResourceString.Net/FodyWeavers.xml b/ResourceString.Net/FodyWeavers.xml
new file mode 100644
index 0000000..f529bfe
--- /dev/null
+++ b/ResourceString.Net/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/ResourceString.Net/Generator.cs b/ResourceString.Net/Generator.cs
new file mode 100644
index 0000000..b7a39f6
--- /dev/null
+++ b/ResourceString.Net/Generator.cs
@@ -0,0 +1,50 @@
+using Microsoft.CodeAnalysis;
+using ResourceString.Net.Logic.Factories;
+using ResourceString.Net.Logic.Parsers.Resx;
+
+namespace ResourceString.Net;
+
+[Generator]
+public class Generator : IIncrementalGenerator
+{
+ public void Initialize(IncrementalGeneratorInitializationContext initContext)
+ {
+ // find all additional files that end with .txt
+ var resxFiles = initContext.AdditionalTextsProvider.Where(static file => file.Path.EndsWith(".resx"));
+ var assemblies = initContext.CompilationProvider.Select(static (c, _) => c.Assembly);
+
+ // read their contents and save their name
+ var config = resxFiles.Combine(assemblies).Select(
+ (t, cancellationToken) => (
+ name: System.IO.Path.GetFileNameWithoutExtension(t.Left.Path),
+ content: t.Left.GetText(cancellationToken)!.ToString(),
+ assembly: t.Right,
+ path: t.Left.Path
+ )
+ );
+
+ // generate a class that contains their values as const strings
+ initContext.RegisterSourceOutput(config, (spc, t) =>
+ {
+ Parser.TryParse(t.content).IfSome(v =>
+ {
+ var relativeNamespace = System.IO.Path.GetDirectoryName(t.path).Substring(
+ System.IO.Path.GetDirectoryName(t.assembly.Locations[0].GetLineSpan().Path).Length
+ ).Trim(System.IO.Path.DirectorySeparatorChar).Replace(System.IO.Path.DirectorySeparatorChar, '.');
+
+ var ns = string.IsNullOrWhiteSpace(relativeNamespace)
+ ? t.assembly.Name
+ : $"{t.assembly.Name}.{relativeNamespace}";
+
+ var snippet = CodeSnippetFactory.CreateResourceClassCodeSnippet(
+ ns,
+ t.name,
+ CodeSnippetFactory.CreateResourceMangerMemberCodeSnippet(t.name),
+ v.Resources
+ );
+
+ spc.AddSource(ns + t.name, snippet.Value);
+ });
+ });
+ }
+}
diff --git a/ResourceString.Net/ResourceString.Net.csproj b/ResourceString.Net/ResourceString.Net.csproj
new file mode 100644
index 0000000..8d42a7c
--- /dev/null
+++ b/ResourceString.Net/ResourceString.Net.csproj
@@ -0,0 +1,41 @@
+
+
+
+ netstandard2.0
+ latest
+ enable
+ true
+ true
+ stubbfel
+ false
+ generator
+ true
+
+
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ all
+
+
+ all
+
+
+ all
+
+
+
+
+
+ all
+
+
+
\ No newline at end of file
diff --git a/ResourceString.Net/deps.nix b/ResourceString.Net/deps.nix
new file mode 100644
index 0000000..99a33c4
--- /dev/null
+++ b/ResourceString.Net/deps.nix
@@ -0,0 +1,48 @@
+{ fetchNuGet }: [
+ (fetchNuGet { pname = "Fody"; version = "6.7.0"; sha256 = "0fv0zrffa296qjyi11yk31vfqh6gm1nxsx8g5zz380jcsrilnp3h"; })
+ (fetchNuGet { pname = "ILMerge.Fody"; version = "1.24.0"; sha256 = "1gibwcl8ngbvwlcqzd9clysrhsjb8g4gwry7n8ifw1mrw7sjjk6x"; })
+ (fetchNuGet { pname = "LanguageExt.Core"; version = "4.4.3"; sha256 = "1pd7wx4c21v56y6i75sxbs990mjrs6bp9h8c48a5w79s1zpbinw5"; })
+ (fetchNuGet { pname = "Microsoft.Bcl.AsyncInterfaces"; version = "7.0.0"; sha256 = "1waiggh3g1cclc81gmjrqbh128kwfjky3z79ma4bd2ms9pa3gvfm"; })
+ (fetchNuGet { pname = "Microsoft.CodeAnalysis.Analyzers"; version = "3.3.4"; sha256 = "0wd6v57p53ahz5z9zg4iyzmy3src7rlsncyqpcag02jjj1yx6g58"; })
+ (fetchNuGet { pname = "Microsoft.CodeAnalysis.Common"; version = "4.3.0"; sha256 = "0qpxygiq53v2d2wl6hccnkjf1lhlxjh4q3w5b6d23aq9pw5qj626"; })
+ (fetchNuGet { pname = "Microsoft.CodeAnalysis.CSharp"; version = "4.3.0"; sha256 = "0m9qqn391ayfi1ffkzvhpij790hs96q6dbhzfkj2ahvw6qx47b30"; })
+ (fetchNuGet { pname = "Microsoft.CSharp"; version = "4.7.0"; sha256 = "0gd67zlw554j098kabg887b5a6pq9kzavpa3jjy5w53ccjzjfy8j"; })
+ (fetchNuGet { pname = "Microsoft.NETCore.Platforms"; version = "1.1.0"; sha256 = "08vh1r12g6ykjygq5d3vq09zylgb84l63k49jc4v8faw9g93iqqm"; })
+ (fetchNuGet { pname = "Microsoft.NETCore.Targets"; version = "1.1.0"; sha256 = "193xwf33fbm0ni3idxzbr5fdq3i2dlfgihsac9jj7whj0gd902nh"; })
+ (fetchNuGet { pname = "NETStandard.Library"; version = "2.0.3"; sha256 = "1fn9fxppfcg4jgypp2pmrpr6awl3qz1xmnri0cygpkwvyx27df1y"; })
+ (fetchNuGet { pname = "System.Buffers"; version = "4.5.1"; sha256 = "04kb1mdrlcixj9zh1xdi5as0k0qi8byr5mi3p3jcxx72qz93s2y3"; })
+ (fetchNuGet { pname = "System.Collections"; version = "4.3.0"; sha256 = "19r4y64dqyrq6k4706dnyhhw7fs24kpp3awak7whzss39dakpxk9"; })
+ (fetchNuGet { pname = "System.Collections.Immutable"; version = "6.0.0"; sha256 = "1js98kmjn47ivcvkjqdmyipzknb9xbndssczm8gq224pbaj1p88c"; })
+ (fetchNuGet { pname = "System.Diagnostics.Contracts"; version = "4.3.0"; sha256 = "1gxawcr4d2y5jmc6y7iv8c1q83hm22f6savcvspvhmpl974jigib"; })
+ (fetchNuGet { pname = "System.Diagnostics.Debug"; version = "4.3.0"; sha256 = "00yjlf19wjydyr6cfviaph3vsjzg3d5nvnya26i2fvfg53sknh3y"; })
+ (fetchNuGet { pname = "System.Globalization"; version = "4.3.0"; sha256 = "1cp68vv683n6ic2zqh2s1fn4c2sd87g5hpp6l4d4nj4536jz98ki"; })
+ (fetchNuGet { pname = "System.IO"; version = "4.3.0"; sha256 = "05l9qdrzhm4s5dixmx68kxwif4l99ll5gqmh7rqgw554fx0agv5f"; })
+ (fetchNuGet { pname = "System.Linq"; version = "4.3.0"; sha256 = "1w0gmba695rbr80l1k2h4mrwzbzsyfl2z4klmpbsvsg5pm4a56s7"; })
+ (fetchNuGet { pname = "System.Linq.Expressions"; version = "4.3.0"; sha256 = "0ky2nrcvh70rqq88m9a5yqabsl4fyd17bpr63iy2mbivjs2nyypv"; })
+ (fetchNuGet { pname = "System.Linq.Queryable"; version = "4.3.0"; sha256 = "0vidv9cjwy8scabxd33mm4zl5vql695rz56ydc42m9b731xi2ahj"; })
+ (fetchNuGet { pname = "System.Memory"; version = "4.5.4"; sha256 = "14gbbs22mcxwggn0fcfs1b062521azb9fbb7c113x0mq6dzq9h6y"; })
+ (fetchNuGet { pname = "System.Memory"; version = "4.5.5"; sha256 = "08jsfwimcarfzrhlyvjjid61j02irx6xsklf32rv57x2aaikvx0h"; })
+ (fetchNuGet { pname = "System.Numerics.Vectors"; version = "4.4.0"; sha256 = "0rdvma399070b0i46c4qq1h2yvjj3k013sqzkilz4bz5cwmx1rba"; })
+ (fetchNuGet { pname = "System.ObjectModel"; version = "4.3.0"; sha256 = "191p63zy5rpqx7dnrb3h7prvgixmk168fhvvkkvhlazncf8r3nc2"; })
+ (fetchNuGet { pname = "System.Reflection"; version = "4.3.0"; sha256 = "0xl55k0mw8cd8ra6dxzh974nxif58s3k1rjv1vbd7gjbjr39j11m"; })
+ (fetchNuGet { pname = "System.Reflection.Emit"; version = "4.7.0"; sha256 = "121l1z2ypwg02yz84dy6gr82phpys0njk7yask3sihgy214w43qp"; })
+ (fetchNuGet { pname = "System.Reflection.Emit.ILGeneration"; version = "4.3.0"; sha256 = "0w1n67glpv8241vnpz1kl14sy7zlnw414aqwj4hcx5nd86f6994q"; })
+ (fetchNuGet { pname = "System.Reflection.Emit.ILGeneration"; version = "4.7.0"; sha256 = "0l8jpxhpgjlf1nkz5lvp61r4kfdbhr29qi8aapcxn3izd9wd0j8r"; })
+ (fetchNuGet { pname = "System.Reflection.Emit.Lightweight"; version = "4.7.0"; sha256 = "0mbjfajmafkca47zr8v36brvknzks5a7pgb49kfq2d188pyv6iap"; })
+ (fetchNuGet { pname = "System.Reflection.Extensions"; version = "4.3.0"; sha256 = "02bly8bdc98gs22lqsfx9xicblszr2yan7v2mmw3g7hy6miq5hwq"; })
+ (fetchNuGet { pname = "System.Reflection.Metadata"; version = "5.0.0"; sha256 = "17qsl5nanlqk9iz0l5wijdn6ka632fs1m1fvx18dfgswm258r3ss"; })
+ (fetchNuGet { pname = "System.Reflection.Primitives"; version = "4.3.0"; sha256 = "04xqa33bld78yv5r93a8n76shvc8wwcdgr1qvvjh959g3rc31276"; })
+ (fetchNuGet { pname = "System.Reflection.TypeExtensions"; version = "4.3.0"; sha256 = "0y2ssg08d817p0vdag98vn238gyrrynjdj4181hdg780sif3ykp1"; })
+ (fetchNuGet { pname = "System.Resources.Extensions"; version = "7.0.0"; sha256 = "0d5gk5g5qqkwa728jwx9yabgjvgywsy6k8r5vgqv2dmlvjrqflb4"; })
+ (fetchNuGet { pname = "System.Resources.ResourceManager"; version = "4.3.0"; sha256 = "0sjqlzsryb0mg4y4xzf35xi523s4is4hz9q4qgdvlvgivl7qxn49"; })
+ (fetchNuGet { pname = "System.Runtime"; version = "4.3.0"; sha256 = "066ixvgbf2c929kgknshcxqj6539ax7b9m570cp8n179cpfkapz7"; })
+ (fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "4.5.3"; sha256 = "1afi6s2r1mh1kygbjmfba6l4f87pi5sg13p4a48idqafli94qxln"; })
+ (fetchNuGet { pname = "System.Runtime.CompilerServices.Unsafe"; version = "6.0.0"; sha256 = "0qm741kh4rh57wky16sq4m0v05fxmkjjr87krycf5vp9f0zbahbc"; })
+ (fetchNuGet { pname = "System.Runtime.Extensions"; version = "4.3.0"; sha256 = "1ykp3dnhwvm48nap8q23893hagf665k0kn3cbgsqpwzbijdcgc60"; })
+ (fetchNuGet { pname = "System.Text.Encoding"; version = "4.3.0"; sha256 = "1f04lkir4iladpp51sdgmis9dj4y8v08cka0mbmsy0frc9a4gjqr"; })
+ (fetchNuGet { pname = "System.Text.Encoding.CodePages"; version = "6.0.0"; sha256 = "0gm2kiz2ndm9xyzxgi0jhazgwslcs427waxgfa30m7yqll1kcrww"; })
+ (fetchNuGet { pname = "System.Threading"; version = "4.3.0"; sha256 = "0rw9wfamvhayp5zh3j7p1yfmx9b5khbf4q50d8k5rk993rskfd34"; })
+ (fetchNuGet { pname = "System.Threading.Tasks"; version = "4.3.0"; sha256 = "134z3v9abw3a6jsw17xl3f6hqjpak5l682k2vz39spj4kmydg6k7"; })
+ (fetchNuGet { pname = "System.Threading.Tasks.Extensions"; version = "4.5.4"; sha256 = "0y6ncasgfcgnjrhynaf0lwpkpkmv4a07sswwkwbwb5h7riisj153"; })
+ (fetchNuGet { pname = "System.ValueTuple"; version = "4.5.0"; sha256 = "00k8ja51d0f9wrq4vv5z2jhq8hy31kac2rg0rv06prylcybzl8cy"; })
+]
diff --git a/alias.sh b/alias.sh
new file mode 100644
index 0000000..2edc66a
--- /dev/null
+++ b/alias.sh
@@ -0,0 +1,6 @@
+#!/bin/env sh
+
+alias nixe='nix --experimental-features "nix-command flakes"'
+alias nulock='nixe run .#devTasks.updateNugetLock'
+alias fllock='nixe run .#devTasks.updateFlakeLock'
+alias ulock='nulock && fllock'
\ No newline at end of file
diff --git a/example/Example.App/Example.App.csproj b/example/Example.App/Example.App.csproj
new file mode 100644
index 0000000..24a4f0f
--- /dev/null
+++ b/example/Example.App/Example.App.csproj
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+ Exe
+ net6.0
+ enable
+ enable
+ latest
+ false
+
+
+
+ $(AdditionalFileItemNames);EmbeddedResource
+
+
+
+
+
+
diff --git a/example/Example.App/Program.cs b/example/Example.App/Program.cs
new file mode 100644
index 0000000..7b3bc36
--- /dev/null
+++ b/example/Example.App/Program.cs
@@ -0,0 +1,22 @@
+namespace Test
+{
+
+ internal class Program
+ {
+ private static void Main(string[] args)
+ {
+ Console.WriteLine("Hello");
+
+ var foo = Example.App.Resources.Test1;
+ Console.WriteLine(foo.Value);
+
+ var bar = Example.App.Properties.SubProperties.Properties.Test2.From(
+ ResourceString.Net.Contract.LiteralString.Factory("1"),
+ ResourceString.Net.Contract.LiteralString.Factory("2")
+ );
+
+ Console.WriteLine(bar.Value);
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/example/Example.App/Properties/SubProperties/Properties.resx b/example/Example.App/Properties/SubProperties/Properties.resx
new file mode 100644
index 0000000..514f90a
--- /dev/null
+++ b/example/Example.App/Properties/SubProperties/Properties.resx
@@ -0,0 +1,14 @@
+
+
+
+ Value1
+ This is a greeting message.
+
+
+ {1}Value2{0}{{2}}
+ 2 = prefix
+
+
+ 0xDEADBEEF
+
+
\ No newline at end of file
diff --git a/example/Example.App/Resources.resx b/example/Example.App/Resources.resx
new file mode 100644
index 0000000..514f90a
--- /dev/null
+++ b/example/Example.App/Resources.resx
@@ -0,0 +1,14 @@
+
+
+
+ Value1
+ This is a greeting message.
+
+
+ {1}Value2{0}{{2}}
+ 2 = prefix
+
+
+ 0xDEADBEEF
+
+
\ No newline at end of file
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..bc6a680
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,26 @@
+{
+ "nodes": {
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1684364070,
+ "narHash": "sha256-+bmqPSEQBePWwmfwxUX8kvJLyg8OM9mRKnDi5qB+m1s=",
+ "owner": "NixOS",
+ "repo": "nixpkgs",
+ "rev": "2e6eb88c9ab70147e6087d37c833833fd4a907e5",
+ "type": "github"
+ },
+ "original": {
+ "id": "nixpkgs",
+ "ref": "nixos-22.11-small",
+ "type": "indirect"
+ }
+ },
+ "root": {
+ "inputs": {
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..c72218c
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,158 @@
+{
+ description = "";
+ inputs.nixpkgs.url = "nixpkgs/nixos-22.11-small";
+ outputs = { self, nixpkgs }:
+ let
+ name = "ResourceString.Net";
+ version = "0.0.1";
+ supportedSystems =
+ [ "x86_64-linux" "x86_64-darwin" "aarch64-linux" "aarch64-darwin" ];
+ forAllSystems = nixpkgs.lib.genAttrs supportedSystems;
+ nixpkgsFor = forAllSystems (system: import nixpkgs { inherit system; });
+ scripts = forAllSystems (system: {
+ updateFlakeLockFile = nixpkgsFor.${system}.writeScript "update_flake_lock.sh" ''
+ nix --experimental-features 'nix-command flakes' flake lock --update-input nixpkgs
+ nix --experimental-features 'nix-command flakes' build
+ '';
+
+ updateNugetLockFile = nixpkgsFor.${system}.writeScript "update_nuget_lock.sh" ''
+ temp_package_folder=$(mktemp -d)
+ dotnet restore ResourceString.Net --packages "$temp_package_folder"
+ ${nixpkgsFor.${system}.nuget-to-nix}/bin/nuget-to-nix "$temp_package_folder" > ResourceString.Net/deps.nix
+ rm -rf "$temp_package_folder"
+ '';
+
+ autoTag = nixpkgsFor.${system}.writeScript "auto_tag.sh" ''
+ git tag --force v${version}
+ git push origin v${version}
+ '';
+ updateLogicResourceFile = nixpkgsFor.${system}.writeScript "update_logic_resource_file.sh" ''
+ dotnet run --project ResourceString.Net.App.Console ResourceString.Net.Logic/Properties/Resources.resx ResourceString.Net.Logic.Properties > ResourceString.Net.Logic/Properties/Resources.cs.tmp
+ mv ResourceString.Net.Logic/Properties/Resources.cs.tmp ResourceString.Net.Logic/Properties/Resources.cs
+ '';
+ runAllTests = nixpkgsFor.${system}.writeScript "run_all_tests.sh" ''
+ dotnet test *.Tests
+ '';
+ buildAllAssemblies = nixpkgsFor.${system}.writeScript "build_all_assemblies.sh" ''
+ # Find all .csproj files below the current folder
+ csproj_files=$(find . -name '*.csproj')
+
+ # Create a temporary file to store the number of dependencies for each project
+ temp_file=$(mktemp)
+
+ # Loop through each .csproj file and count its number of ResourceString dependencies
+ for file in $csproj_files
+ do
+ count=$(cat "$file" | grep -c "> "$temp_file"
+ done
+
+ # Sort the projects by their count of dependencies and path depth, in ascending order
+ sorted_projects=$(sort -nk1 -nk2 -nk3 -t' ' "$temp_file" | awk '{print $4}')
+
+ # Loop through the sorted projects and build them
+ while read -r file
+ do
+ echo "Building project: $file"
+ dotnet build "$file"
+ done << EOF
+ $sorted_projects
+ EOF
+
+ # Remove the temporary file
+ rm "$temp_file"
+
+ '';
+
+ cleanAllAssemblies = nixpkgsFor.${system}.writeScript "clean_all_assemblies.sh" ''
+ csproj_files=$(find . -name '*.csproj')
+ for file in $csproj_files
+ do
+ dotnet clean "$file"
+ done
+ '';
+ runDefaultApp = nixpkgsFor.${system}.writeScript "run_default_app.sh" ''
+ dotnet run --project ResourceString.Net.App.Console $@
+ '';
+ createNugetPackage = nixpkgsFor.${system}.writeScript "run_default_app.sh" ''
+ dotnet run --project ResourceString.Net.App.Console $@
+ '';
+ });
+
+ in
+ rec {
+ packages = forAllSystems (system: {
+ default = nixpkgsFor.${system}.buildDotnetModule {
+ pname = name;
+ version = version;
+
+ src = self;
+
+ projectFile = [
+ "ResourceString.Net/ResourceString.Net.csproj"
+ "ResourceString.Net.App.Console/ResourceString.Net.App.Console.csproj"
+ "example/Example.App/Example.App.csproj"
+ ];
+
+ nugetDeps = ./ResourceString.Net/deps.nix;
+ projectReferences = [ ];
+
+ dotnet-sdk = nixpkgsFor.${system}.dotnet-sdk;
+ dotnet-runtime = nixpkgsFor.${system}.dotnet-runtime;
+ executables = [ "ResourceString.Net.App.Console" "Example.App" ];
+ packNupkg = true;
+ runtimeDeps = [ ];
+ };
+ });
+
+ apps = forAllSystems (system: {
+ default = {
+ type = "app";
+ program = "${scripts.${system}.runDefaultApp}";
+ };
+ devTasks = {
+ updateFlakeLock = {
+ type = "app";
+ program = "${scripts.${system}.updateFlakeLockFile}";
+ };
+ updateNugetLock = {
+ type = "app";
+ program = "${scripts.${system}.updateNugetLockFile}";
+ };
+ autoTag = {
+ type = "app";
+ program = "${scripts.${system}.autoTag}";
+ };
+ updateLogicResourceFile = {
+ type = "app";
+ program = "${scripts.${system}.updateLogicResourceFile}";
+ };
+ runAllTests = {
+ type = "app";
+ program = "${scripts.${system}.runAllTests}";
+ };
+ buildAllAssemblies = {
+ type = "app";
+ program = "${scripts.${system}.buildAllAssemblies}";
+ };
+ cleanAllAssemblies = {
+ type = "app";
+ program = "${scripts.${system}.cleanAllAssemblies}";
+ };
+ };
+ });
+
+ devShells = forAllSystems (system: {
+ default = nixpkgsFor.${system}.mkShell {
+ name = "dev-shell";
+ packages =
+ [ nixpkgsFor.${system}.dotnet-sdk nixpkgsFor.${system}.nixfmt ];
+ shellHook = ''
+ alias nixe="nix --experimental-features 'nix-command flakes'"
+ '';
+ };
+ });
+ };
+}