diff --git a/Directory.Packages.props b/Directory.Packages.props
index 22956ca..c49a45e 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -12,10 +12,14 @@
+
+
+
+
diff --git a/StringTemplates.slnx b/StringTemplates.slnx
index d2cf459..5bde359 100644
--- a/StringTemplates.slnx
+++ b/StringTemplates.slnx
@@ -19,9 +19,16 @@
-
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/StringTemplates.Demo/Program.cs b/examples/StringTemplates.Demo/Program.cs
index a6eb318..75e7802 100644
--- a/examples/StringTemplates.Demo/Program.cs
+++ b/examples/StringTemplates.Demo/Program.cs
@@ -1,11 +1,13 @@
using Microsoft.AspNetCore.Mvc;
using StringTemplates.Extensions;
+using StringTemplates.Plugins.Configuration;
using StringTemplates.Services;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
-builder.Services.AddStringTemplates();
+builder.Services.AddStringTemplates(options => options.AddPlugins(opts =>
+ opts.AddPluginSingleton()));
// Learn more about configuring OpenAPI at https://aka.ms/aspnet/openapi
builder.Services.AddOpenApi();
@@ -19,7 +21,7 @@
}
// Map endpoints
-app.MapPost("system", (
+app.MapPost("general", (
[FromBody] SystemRequest request,
[FromServices] ITemplateService templateService) =>
Results.Ok(templateService.ReplacePlaceholders(request.Template)));
diff --git a/examples/StringTemplates.Demo/StringTemplates.Demo.csproj b/examples/StringTemplates.Demo/StringTemplates.Demo.csproj
index 263b40d..a8107ae 100644
--- a/examples/StringTemplates.Demo/StringTemplates.Demo.csproj
+++ b/examples/StringTemplates.Demo/StringTemplates.Demo.csproj
@@ -5,6 +5,7 @@
+
diff --git a/examples/StringTemplates.Demo/StringTemplates.http b/examples/StringTemplates.Demo/StringTemplates.http
index 5368867..31d1de9 100644
--- a/examples/StringTemplates.Demo/StringTemplates.http
+++ b/examples/StringTemplates.Demo/StringTemplates.http
@@ -1,5 +1,5 @@
-### POST 1 request to use system template
-POST https://localhost:7228/system
+### POST 1 request to use generic plugins
+POST https://localhost:7228/general
Content-Type: application/json
{
@@ -7,12 +7,21 @@ Content-Type: application/json
}
###
-### POST 2 request to use system template
-POST https://localhost:7228/system
+### POST 2 request to use generic plugins
+POST https://localhost:7228/general
Content-Type: application/json
{
"template": "The time and date now is \u007B\u007B#System#Time.Now#System#\u007D\u007D of \u007B\u007B#System#Day.OfMonth#System#\u007D\u007Dth of \u007B\u007B#System#Month#System#\u007D\u007D \u007B\u007B#System#Year#System#\u007D\u007D."
}
###
+
+### POST 3 request to use generic plugins
+POST https://localhost:7228/general
+Content-Type: application/json
+
+{
+ "template": "This project was created in \u007B\u007B#System#Year#System#\u007D\u007D and the database used is '\u007B\u007B#Configuration#ConnectionStrings.DefaultConnection#Configuration#\u007D\u007D'."
+}
+###
###
diff --git a/examples/StringTemplates.Demo/appsettings.json b/examples/StringTemplates.Demo/appsettings.json
index 10f68b8..8503740 100644
--- a/examples/StringTemplates.Demo/appsettings.json
+++ b/examples/StringTemplates.Demo/appsettings.json
@@ -5,5 +5,9 @@
"Microsoft.AspNetCore": "Warning"
}
},
+ "Random:Value": "This is a random value from appsettings.json",
+ "ConnectionStrings": {
+ "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=aspnet-StringTemplates.Demo;Trusted_Connection=True;MultipleActiveResultSets=true"
+ },
"AllowedHosts": "*"
}
diff --git a/src/StringTemplates/StringTemplates.csproj b/src/StringTemplates/StringTemplates.csproj
index 2b9425b..45d20b2 100644
--- a/src/StringTemplates/StringTemplates.csproj
+++ b/src/StringTemplates/StringTemplates.csproj
@@ -8,7 +8,7 @@
Stratis-Dermanoutsos
Stratis-Dermanoutsos
- Copyright (c) Stratis Dermanoutsos 2025
+ Copyright © Stratis Dermanoutsos 2025
LICENSE
https://github.com/Stratis-OSS/StringTemplates.Net
diff --git a/src/plugins/StringTemplates.Plugins.Configuration/ConfigurationTemplatePlugin.cs b/src/plugins/StringTemplates.Plugins.Configuration/ConfigurationTemplatePlugin.cs
new file mode 100644
index 0000000..f23fd22
--- /dev/null
+++ b/src/plugins/StringTemplates.Plugins.Configuration/ConfigurationTemplatePlugin.cs
@@ -0,0 +1,33 @@
+using Microsoft.Extensions.Configuration;
+using StringTemplates.Services;
+
+namespace StringTemplates.Plugins.Configuration;
+
+///
+/// implementation for handling configuration values inside of template strings.
+///
+/// Placeholders are detected using the Configuration tag, E.g.
+///
+/// - {{#Configuration#ConnectionStrings.Database#Configuration#}}
+/// - {{#Configuration#KeyVault.Auth.ClientId#Configuration#}}
+///
+///
+/// The application's instance to read values from.
+///
+/// For internal use only.
+/// Not recommended to expose this plugin to outside users as it could be used to expose Environment Variables.
+///
+///
+/// Separators in keys can be a dot (.), e.g. ConnectionStrings.Database.
+/// The plugin later replaces dots with colons (:) to match the format used by .
+/// E.g. ConnectionStrings:Database.
+///
+public sealed class ConfigurationTemplatePlugin(IConfiguration configuration) : ITemplatePlugin
+{
+ public string PlaceholderTag => "Configuration";
+
+ public string? GetValueOrDefault(string placeholder)
+ {
+ return configuration[placeholder.Replace(".", ":")];
+ }
+}
diff --git a/src/plugins/StringTemplates.Plugins.Configuration/StringTemplates.Plugins.Configuration.csproj b/src/plugins/StringTemplates.Plugins.Configuration/StringTemplates.Plugins.Configuration.csproj
new file mode 100644
index 0000000..33c0bad
--- /dev/null
+++ b/src/plugins/StringTemplates.Plugins.Configuration/StringTemplates.Plugins.Configuration.csproj
@@ -0,0 +1,37 @@
+
+
+
+ StringTemplates.Configuration
+ StringTemplates.Configuration
+ 9.0.9
+
+ Stratis-Dermanoutsos
+ Stratis-Dermanoutsos
+
+ Copyright © Stratis Dermanoutsos 2025
+ LICENSE
+
+ https://github.com/Stratis-OSS/StringTemplates.Net
+ https://github.com/Stratis-OSS/StringTemplates.Net
+
+ The best way to handle templates and replace placeholders using IConfiguration.
+ open-source oss csharp dotnet strings templates placeholders environment config
+ README.md
+ * First release.
+ * IConfiguration plugin.
+
+
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/tests/StringTemplates.IntegrationTests/ApiFactory.cs b/tests/StringTemplates.IntegrationTests.Common/ApiFactory.cs
similarity index 82%
rename from tests/StringTemplates.IntegrationTests/ApiFactory.cs
rename to tests/StringTemplates.IntegrationTests.Common/ApiFactory.cs
index d632876..d541f06 100644
--- a/tests/StringTemplates.IntegrationTests/ApiFactory.cs
+++ b/tests/StringTemplates.IntegrationTests.Common/ApiFactory.cs
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Mvc.Testing;
-namespace StringTemplates.IntegrationTests;
+namespace StringTemplates.IntegrationTests.Common;
public class ApiFactory : WebApplicationFactory
{
diff --git a/tests/StringTemplates.IntegrationTests/Services/Abstractions/ApiTests.cs b/tests/StringTemplates.IntegrationTests.Common/Services/Abstractions/ApiTests.cs
similarity index 81%
rename from tests/StringTemplates.IntegrationTests/Services/Abstractions/ApiTests.cs
rename to tests/StringTemplates.IntegrationTests.Common/Services/Abstractions/ApiTests.cs
index d123f77..99280ae 100644
--- a/tests/StringTemplates.IntegrationTests/Services/Abstractions/ApiTests.cs
+++ b/tests/StringTemplates.IntegrationTests.Common/Services/Abstractions/ApiTests.cs
@@ -1,6 +1,7 @@
using System.Net.Http.Json;
-namespace StringTemplates.IntegrationTests.Services.Abstractions;
+// ReSharper disable once CheckNamespace
+namespace StringTemplates.IntegrationTests.Common;
public abstract class ApiTests(ApiFactory factory)
{
diff --git a/tests/StringTemplates.IntegrationTests.Common/StringTemplates.IntegrationTests.Common.csproj b/tests/StringTemplates.IntegrationTests.Common/StringTemplates.IntegrationTests.Common.csproj
new file mode 100644
index 0000000..fe18f10
--- /dev/null
+++ b/tests/StringTemplates.IntegrationTests.Common/StringTemplates.IntegrationTests.Common.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/tests/StringTemplates.IntegrationTests/Services/DictionaryServiceTests.cs b/tests/StringTemplates.IntegrationTests/Services/DictionaryServiceTests.cs
index 785369e..6542e68 100644
--- a/tests/StringTemplates.IntegrationTests/Services/DictionaryServiceTests.cs
+++ b/tests/StringTemplates.IntegrationTests/Services/DictionaryServiceTests.cs
@@ -1,5 +1,5 @@
using Shouldly;
-using StringTemplates.IntegrationTests.Services.Abstractions;
+using StringTemplates.IntegrationTests.Common;
namespace StringTemplates.IntegrationTests.Services;
diff --git a/tests/StringTemplates.IntegrationTests/Services/SystemServiceTests.cs b/tests/StringTemplates.IntegrationTests/Services/SystemServiceTests.cs
index 5b84634..b15bfbe 100644
--- a/tests/StringTemplates.IntegrationTests/Services/SystemServiceTests.cs
+++ b/tests/StringTemplates.IntegrationTests/Services/SystemServiceTests.cs
@@ -1,6 +1,6 @@
using System.Globalization;
using Shouldly;
-using StringTemplates.IntegrationTests.Services.Abstractions;
+using StringTemplates.IntegrationTests.Common;
namespace StringTemplates.IntegrationTests.Services;
@@ -14,7 +14,7 @@ public async Task System_Date_Now_Replaced()
var payload = new { template = "Today is {{#System#Date.Now#System#}}." };
// Act
- var result = await PostForStringAsync("system", payload);
+ var result = await PostForStringAsync("general", payload);
// Assert
result.ShouldBe($"Today is {expected}.");
@@ -28,7 +28,7 @@ public async Task System_Date_UtcNow_Replaced()
var payload = new { template = "UTC date: {{#System#Date.UtcNow#System#}}" };
// Act
- var result = await PostForStringAsync("system", payload);
+ var result = await PostForStringAsync("general", payload);
// Assert
result.ShouldBe($"UTC date: {expected}");
@@ -41,7 +41,7 @@ public async Task System_DateTime_Now_Format_Is_Correct()
var payload = new { template = "[{{#System#DateTime.Now#System#}}]" };
// Act
- var result = await PostForStringAsync("system", payload);
+ var result = await PostForStringAsync("general", payload);
// Assert
result.ShouldMatch(@"^\[\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}\]$");
@@ -54,7 +54,7 @@ public async Task System_DateTime_UtcNow_Format_Is_Correct()
var payload = new { template = "<{{#System#DateTime.UtcNow#System#}}>" };
// Act
- var result = await PostForStringAsync("system", payload);
+ var result = await PostForStringAsync("general", payload);
// Assert
result.ShouldMatch(@"^<\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}>$");
@@ -71,8 +71,8 @@ public async Task System_Day_And_Month_Replaced()
var monthPayload = new { template = "It is {{#System#Month#System#}} now." };
// Act
- var dayResult = await PostForStringAsync("system", dayPayload);
- var monthResult = await PostForStringAsync("system", monthPayload);
+ var dayResult = await PostForStringAsync("general", dayPayload);
+ var monthResult = await PostForStringAsync("general", monthPayload);
// Assert
dayResult.ShouldBe($"Happy {expectedDay}!");
@@ -86,7 +86,7 @@ public async Task System_Time_Now_Format_Is_Correct()
var payload = new { template = "time={{#System#Time.Now#System#}}" };
// Act
- var result = await PostForStringAsync("system", payload);
+ var result = await PostForStringAsync("general", payload);
// Assert
result.ShouldMatch(@"^time=\d{2}:\d{2}:\d{2}$");
@@ -99,7 +99,7 @@ public async Task System_Time_UtcNow_Format_Is_Correct()
var payload = new { template = "utc={{#System#Time.UtcNow#System#}}" };
// Act
- var result = await PostForStringAsync("system", payload);
+ var result = await PostForStringAsync("general", payload);
// Assert
result.ShouldMatch(@"^utc=\d{2}:\d{2}:\d{2}$");
@@ -113,7 +113,7 @@ public async Task System_Unknown_Placeholder_Remains()
var payload = new { template };
// Act
- var result = await PostForStringAsync("system", payload);
+ var result = await PostForStringAsync("general", payload);
// Assert
result.ShouldBe(template);
diff --git a/tests/StringTemplates.IntegrationTests/StringTemplates.IntegrationTests.csproj b/tests/StringTemplates.IntegrationTests/StringTemplates.IntegrationTests.csproj
index 1483151..ab26cd6 100644
--- a/tests/StringTemplates.IntegrationTests/StringTemplates.IntegrationTests.csproj
+++ b/tests/StringTemplates.IntegrationTests/StringTemplates.IntegrationTests.csproj
@@ -6,7 +6,6 @@
-
@@ -18,8 +17,8 @@
-
+
\ No newline at end of file
diff --git a/tests/StringTemplates.UnitTests/Services/Plugins/DictionaryTemplatePluginTests.cs b/tests/StringTemplates.UnitTests/Services/Plugins/DictionaryTemplatePluginTests.cs
index 04f65ae..87dad95 100644
--- a/tests/StringTemplates.UnitTests/Services/Plugins/DictionaryTemplatePluginTests.cs
+++ b/tests/StringTemplates.UnitTests/Services/Plugins/DictionaryTemplatePluginTests.cs
@@ -1,5 +1,5 @@
using System.Globalization;
-using StringTemplates.Services;
+using Shouldly;
using StringTemplates.Services.Plugins;
namespace StringTemplates.UnitTests.Services.Plugins;
@@ -10,22 +10,25 @@ public class DictionaryTemplatePluginTests
public void Dictionary_Replaces_Single_Key()
{
// Arrange
+ var expected = "A value";
var dict = new Dictionary
{
- ["Name"] = "Stratis"
+ ["Name"] = expected
};
- var template = "Hello, {{#Dictionary#Name#Dictionary#}}! Welcome to StringTemplates.";
- ITemplateService> sut = new DictionaryTemplatePlugin();
+ var plugin = new DictionaryTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template, dict);
+ var result = plugin.GetValueOrDefault("Name", dict);
// Assert
- Assert.Equal("Hello, Stratis! Welcome to StringTemplates.", result);
+ result.ShouldBe(expected);
}
- [Fact]
- public void Dictionary_Replaces_Multiple_Keys()
+ [Theory]
+ [InlineData("FirstName", "Alex")]
+ [InlineData("LastName", "Papadopoulos")]
+ [InlineData("Role", "Administrator")]
+ public void Dictionary_Replaces_Multiple_Keys(string key, string expected)
{
// Arrange
var dict = new Dictionary
@@ -34,113 +37,71 @@ public void Dictionary_Replaces_Multiple_Keys()
["LastName"] = "Papadopoulos",
["Role"] = "Administrator"
};
- var template =
- "User: {{#Dictionary#FirstName#Dictionary#}} {{#Dictionary#LastName#Dictionary#}}.\n" +
- "Current role: {{#Dictionary#Role#Dictionary#}}.";
- ITemplateService> sut = new DictionaryTemplatePlugin();
+ var plugin = new DictionaryTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template, dict);
+ var result = plugin.GetValueOrDefault(key, dict);
// Assert
- var expected =
- "User: Alex Papadopoulos.\n" +
- "Current role: Administrator.";
- Assert.Equal(expected, result);
+ result.ShouldBe(expected);
}
- [Fact]
- public void Dictionary_Replaces_Mixed_Types_And_Repeats()
+ [Theory]
+ [InlineData("OrderId", "1729")]
+ [InlineData("Total", "123.45")]
+ [InlineData("Currency", "EUR")]
+ public void Dictionary_Replaces_Mixed_Types_And_Repeats(string key, string expected)
{
// Arrange
+ var culture = CultureInfo.InvariantCulture;
+ CultureInfo.CurrentCulture = culture;
+ CultureInfo.CurrentUICulture = culture;
var dict = new Dictionary
{
["OrderId"] = 1729,
["Total"] = 123.45m,
["Currency"] = "EUR"
};
- var template =
- "Order #{{#Dictionary#OrderId#Dictionary#}} has been processed successfully. " +
- "Amount charged: {{#Dictionary#Total#Dictionary#}} {{#Dictionary#Currency#Dictionary#}}. " +
- "Reference: ORD-{{#Dictionary#OrderId#Dictionary#}}.";
- ITemplateService> sut = new DictionaryTemplatePlugin();
+ var plugin = new DictionaryTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template, dict);
+ var result = plugin.GetValueOrDefault(key, dict);
// Assert
- var totalFormatted = ((decimal)dict["Total"]).ToString(CultureInfo.CurrentCulture);
- var expected =
- $"Order #1729 has been processed successfully. " +
- $"Amount charged: {totalFormatted} EUR. " +
- $"Reference: ORD-1729.";
- Assert.Equal(expected, result);
+ result.ShouldBe(expected);
}
- [Fact]
- public void Dictionary_Unknown_Key_Remains()
+ [Theory]
+ [InlineData("Known", "OK")]
+ [InlineData("NotThere", null)]
+ public void Dictionary_Known_And_Unknown_Keys_Remain_Or_Are_Replaced(string key, string? expected)
{
// Arrange
var dict = new Dictionary
{
["Known"] = "OK"
};
- var template =
- "Known: {{#Dictionary#Known#Dictionary#}}; " +
- "Unknown: {{#Dictionary#NotThere#Dictionary#}}.";
- ITemplateService> sut = new DictionaryTemplatePlugin();
+ var plugin = new DictionaryTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template, dict);
+ var result = plugin.GetValueOrDefault(key, dict);
// Assert
- var expected =
- "Known: OK; " +
- "Unknown: {{#Dictionary#NotThere#Dictionary#}}.";
- Assert.Equal(expected, result);
- }
-
- [Fact]
- public void Dictionary_Replaces_Values_In_Long_Text()
- {
- // Arrange
- var start = new DateTime(2025, 09, 16, 8, 30, 0);
- var dict = new Dictionary
- {
- ["Company"] = "Lygom",
- ["Position"] = "Senior Engineer",
- ["StartDate"] = start // ToString() is used by the plugin
- };
-
- var template =
- "Congratulations, {{#Dictionary#Position#Dictionary#}} at {{#Dictionary#Company#Dictionary#}}!\n" +
- "Your official start date is {{#Dictionary#StartDate#Dictionary#}}. Please check your inbox for onboarding details.";
- ITemplateService> sut = new DictionaryTemplatePlugin();
-
- // Act
- var result = sut.ReplacePlaceholders(template, dict);
-
- // Assert
- var expected =
- $"Congratulations, Senior Engineer at Lygom!\n" +
- $"Your official start date is {start.ToString()}. Please check your inbox for onboarding details.";
- Assert.Equal(expected, result);
+ result.ShouldBe(expected);
}
[Fact]
public void Dictionary_Leaves_Template_As_Is_When_Model_Is_Null()
{
// Arrange
+ string? expected = null;
Dictionary? dict = null;
- var template =
- "Dear {{#Dictionary#FirstName#Dictionary#}}, your ticket {{#Dictionary#TicketId#Dictionary#}} is received.";
-
- ITemplateService> sut = new DictionaryTemplatePlugin();
+ var plugin = new DictionaryTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template, dict);
+ var result = plugin.GetValueOrDefault("SomeKey", dict);
// Assert
- Assert.Equal(template, result);
+ result.ShouldBe(expected);
}
}
diff --git a/tests/StringTemplates.UnitTests/Services/Plugins/SystemTemplatePluginTests.cs b/tests/StringTemplates.UnitTests/Services/Plugins/SystemTemplatePluginTests.cs
index cd4bb56..02316bc 100644
--- a/tests/StringTemplates.UnitTests/Services/Plugins/SystemTemplatePluginTests.cs
+++ b/tests/StringTemplates.UnitTests/Services/Plugins/SystemTemplatePluginTests.cs
@@ -1,7 +1,8 @@
-using System.Globalization;
-using System.Text.RegularExpressions;
+using Shouldly;
using StringTemplates.Services;
using StringTemplates.Services.Plugins;
+using System.Globalization;
+using System.Text.RegularExpressions;
namespace StringTemplates.UnitTests.Services.Plugins;
@@ -12,14 +13,13 @@ public void System_Date_Now_Replaced()
{
// Arrange
var expected = DateTime.Now.ToString("d", CultureInfo.InvariantCulture);
- var template = "Today is {{#System#Date.Now#System#}}.";
- ITemplateService sut = new SystemTemplatePlugin();
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var result = plugin.GetValueOrDefault("Date.Now");
// Assert
- Assert.Equal($"Today is {expected}.", result);
+ result.ShouldBe(expected);
}
[Fact]
@@ -27,44 +27,43 @@ public void System_Date_UtcNow_Replaced()
{
// Arrange
var expected = DateTime.UtcNow.ToString("d", CultureInfo.InvariantCulture);
- var template = "UTC date: {{#System#Date.UtcNow#System#}}";
- ITemplateService sut = new SystemTemplatePlugin();
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var result = plugin.GetValueOrDefault("Date.UtcNow");
// Assert
- Assert.Equal($"UTC date: {expected}", result);
+ result.ShouldBe(expected);
}
[Fact]
public void System_DateTime_Now_Format_Is_Correct()
{
// Arrange
- var template = "[{{#System#DateTime.Now#System#}}]";
- ITemplateService sut = new SystemTemplatePlugin();
+ const string expected = @"^\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}$";
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var value = plugin.GetValueOrDefault("DateTime.Now");
+ var result = Regex.Match(value ?? string.Empty, expected).Success;
// Assert
- var m = Regex.Match(result, @"^\[\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}\]$");
- Assert.True(m.Success, $"Result '{result}' did not match expected DateTime format.");
+ result.ShouldBe(true);
}
[Fact]
public void System_DateTime_UtcNow_Format_Is_Correct()
{
// Arrange
- var template = "<{{#System#DateTime.UtcNow#System#}}>";
- ITemplateService sut = new SystemTemplatePlugin();
+ const string expected = @"^\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}$";
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var value = plugin.GetValueOrDefault("DateTime.UtcNow");
+ var result = Regex.Match(value ?? string.Empty, expected).Success;
// Assert
- var m = Regex.Match(result, @"^<\d{2}/\d{2}/\d{4} \d{2}:\d{2}:\d{2}>$");
- Assert.True(m.Success, $"Result '{result}' did not match expected DateTime format.");
+ result.ShouldBe(true);
}
[Fact]
@@ -72,14 +71,13 @@ public void System_Day_Replaced()
{
// Arrange
var expected = DateTime.Now.DayOfWeek.ToString();
- var template = "Happy {{#System#Day#System#}}!";
- ITemplateService sut = new SystemTemplatePlugin();
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var result = plugin.GetValueOrDefault("Day");
// Assert
- Assert.Equal($"Happy {expected}!", result);
+ result.ShouldBe(expected);
}
[Fact]
@@ -87,72 +85,70 @@ public void System_Month_Replaced()
{
// Arrange
var expected = DateTime.Now.ToString("MMMM", CultureInfo.InvariantCulture);
- var template = "It is {{#System#Month#System#}} now.";
- ITemplateService sut = new SystemTemplatePlugin();
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var result = plugin.GetValueOrDefault("Month");
// Assert
- Assert.Equal($"It is {expected} now.", result);
+ result.ShouldBe(expected);
}
[Fact]
public void System_Time_Now_Format_Is_Correct()
{
// Arrange
- var template = "time={{#System#Time.Now#System#}}";
- ITemplateService sut = new SystemTemplatePlugin();
+ const string expected = @"^\d{2}:\d{2}:\d{2}$";
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var value = plugin.GetValueOrDefault("Time.Now");
+ var result = Regex.Match(value ?? string.Empty, expected).Success;
// Assert
- var m = Regex.Match(result, @"^time=\d{2}:\d{2}:\d{2}$");
- Assert.True(m.Success, $"Result '{result}' did not match expected time format.");
+ result.ShouldBe(true);
}
[Fact]
public void System_Time_UtcNow_Format_Is_Correct()
{
// Arrange
- var template = "utc={{#System#Time.UtcNow#System#}}";
- ITemplateService sut = new SystemTemplatePlugin();
+ const string expected = @"^\d{2}:\d{2}:\d{2}$";
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var value = plugin.GetValueOrDefault("Time.UtcNow");
+ var result = Regex.Match(value ?? string.Empty, expected).Success;
// Assert
- var m = Regex.Match(result, @"^utc=\d{2}:\d{2}:\d{2}$");
- Assert.True(m.Success, $"Result '{result}' did not match expected time format.");
+ result.ShouldBe(true);
}
[Fact]
public void System_Year_Replaced()
{
// Arrange
- var expectedYear = DateTime.Now.Year.ToString();
- var template = "The year is {{#System#Year#System#}} — stay focused.";
- ITemplateService sut = new SystemTemplatePlugin();
+ var expected = DateTime.Now.Year.ToString();
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var result = plugin.GetValueOrDefault("Year");
// Assert
- Assert.Equal($"The year is {expectedYear} — stay focused.", result);
+ result.ShouldBe(expected);
}
[Fact]
- public void System_Unknown_Placeholder_Remains()
+ public void GetValueOrDefault_ReturnsNull_WhenKeyDoesNotExist()
{
// Arrange
- var template = "Unknown -> {{#System#Does.Not.Exist#System#}} <- keep it";
- ITemplateService sut = new SystemTemplatePlugin();
+ string? expected = null;
+ var plugin = new SystemTemplatePlugin();
// Act
- var result = sut.ReplacePlaceholders(template);
+ var result = plugin.GetValueOrDefault("Does.Not.Exist");
// Assert
- Assert.Equal(template, result);
+ result.ShouldBe(expected);
}
}
diff --git a/tests/StringTemplates.UnitTests/StringTemplates.UnitTests.csproj b/tests/StringTemplates.UnitTests/StringTemplates.UnitTests.csproj
index fbd02f9..829dd04 100644
--- a/tests/StringTemplates.UnitTests/StringTemplates.UnitTests.csproj
+++ b/tests/StringTemplates.UnitTests/StringTemplates.UnitTests.csproj
@@ -7,6 +7,7 @@
+
diff --git a/tests/plugins/Configuration/StringTemplates.Plugins.Configuration.UnitTests/Services/Plugins/ConfigurationTemplatePluginTests.cs b/tests/plugins/Configuration/StringTemplates.Plugins.Configuration.UnitTests/Services/Plugins/ConfigurationTemplatePluginTests.cs
new file mode 100644
index 0000000..5c8acd4
--- /dev/null
+++ b/tests/plugins/Configuration/StringTemplates.Plugins.Configuration.UnitTests/Services/Plugins/ConfigurationTemplatePluginTests.cs
@@ -0,0 +1,44 @@
+using Microsoft.Extensions.Configuration;
+using Shouldly;
+
+namespace StringTemplates.Plugins.Configuration.UnitTests.Services.Plugins;
+
+public class ConfigurationTemplatePluginTests
+{
+ [Fact]
+ public void GetValueOrDefault_ReturnsValue_WhenKeyExists()
+ {
+ // Arrange
+ var expected = "Some value";
+ var inMemorySettings = new Dictionary
+ {
+ { "AppName", expected }
+ };
+ IConfiguration config = new ConfigurationBuilder()
+ .AddInMemoryCollection(inMemorySettings)
+ .Build();
+ var plugin = new ConfigurationTemplatePlugin(config);
+
+ // Act
+ var result = plugin.GetValueOrDefault("AppName");
+
+ // Assert
+ result.ShouldBe(expected);
+ }
+
+ [Fact]
+ public void GetValueOrDefault_ReturnsNull_WhenKeyDoesNotExist()
+ {
+ // Arrange
+ IConfiguration config = new ConfigurationBuilder()
+ .AddInMemoryCollection()
+ .Build();
+ var plugin = new ConfigurationTemplatePlugin(config);
+
+ // Act
+ var result = plugin.GetValueOrDefault("DoesNotExist");
+
+ // Assert
+ result.ShouldBeNull();
+ }
+}
diff --git a/tests/plugins/Configuration/StringTemplates.Plugins.Configuration.UnitTests/StringTemplates.Plugins.Configuration.UnitTests.csproj b/tests/plugins/Configuration/StringTemplates.Plugins.Configuration.UnitTests/StringTemplates.Plugins.Configuration.UnitTests.csproj
new file mode 100644
index 0000000..d88eaf3
--- /dev/null
+++ b/tests/plugins/Configuration/StringTemplates.Plugins.Configuration.UnitTests/StringTemplates.Plugins.Configuration.UnitTests.csproj
@@ -0,0 +1,24 @@
+
+
+
+ false
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+