Skip to content

Commit

Permalink
Imported Tingle.Extensions.Mustache (#230)
Browse files Browse the repository at this point in the history
  • Loading branch information
mburumaxwell authored Apr 6, 2024
1 parent f4100fc commit 61fb3e9
Show file tree
Hide file tree
Showing 40 changed files with 2,640 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .codespellrc
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
[codespell]
skip = .git,*/wwwroot,*.g.cs,countries-20200124.json,iso-639-3.tab,iso-4217.json,countries.json,currencies.json,languages.json
# some modules, parts of regexes, and variable names to ignore
ignore-words-list = aci
ignore-words-list = aci,eachs,coppy
1 change: 1 addition & 0 deletions .github/workflows/cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ jobs:
- { name: 'Tingle.Extensions.Http.Authentication' }
- { name: 'Tingle.Extensions.JsonPatch' }
- { name: 'Tingle.Extensions.MongoDB' }
- { name: 'Tingle.Extensions.Mustache' }
- { name: 'Tingle.Extensions.PhoneValidators' }
- { name: 'Tingle.Extensions.Primitives' }
- { name: 'Tingle.Extensions.Processing' }
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ This repository contains projects/libraries for adding useful functionality to .
|[`Tingle.Extensions.Http.Authentication`](https://www.nuget.org/packages/Tingle.Extensions.Http.Authentication/)|Authentication providers for use with `HttpClient` and includes support for DI via `Microsoft.Extensions.Http`. See [docs](./src/Tingle.Extensions.Http.Authentication/README.md) and [sample](./samples/HttpAuthenticationSample).|
|[`Tingle.Extensions.JsonPatch`](https://www.nuget.org/packages/Tingle.Extensions.JsonPatch/)|JSON Patch (RFC 6902) support for .NET to easily generate JSON Patch documents using `System.Text.Json` for client applications. See [docs](./src/Tingle.Extensions.JsonPatch/README.md).|
|[`Tingle.Extensions.MongoDB`](https://www.nuget.org/packages/Tingle.Extensions.MongoDB/)|Extensions for working with MongoDB. See [docs](./src/Tingle.Extensions.MongoDB/README.md) and [sample](./samples/MongoDBSample).|
|[`Tingle.Extensions.Mustache`](https://www.nuget.org/packages/Tingle.Extensions.Mustache/)|Basic mustache cache implementation in .NET built upon the good work offered in at <https://github.com/ActiveCampaign/mustachio>. See [docs](./src/Tingle.Extensions.Mustache/README.md).|
|[`Tingle.Extensions.PhoneValidators`](https://www.nuget.org/packages/Tingle.Extensions.PhoneValidators/)|Convenience for validation of phone numbers either via attributes or resolvable services. See [docs](./src/Tingle.Extensions.PhoneValidators/README.md).|
|[`Tingle.Extensions.Primitives`](https://www.nuget.org/packages/Tingle.Extensions.Primitives/)|Additional primitive types such as `Money`, `Currency`, `Duration`, `Keygen`, `Etag` etc. See [docs](./src/Tingle.Extensions.Primitives/README.md).|
|[`Tingle.Extensions.Processing`](https://www.nuget.org/packages/Tingle.Extensions.Processing/)|Helpers for making processing of bulk in memory tasks. See [docs](./src/Tingle.Extensions.Processing/README.md).|
Expand Down
14 changes: 14 additions & 0 deletions Tingle.Extensions.sln
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.JsonPatch
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.MongoDB", "src\Tingle.Extensions.MongoDB\Tingle.Extensions.MongoDB.csproj", "{17F58DAD-0789-4AD7-921E-24CEA09DC68B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.Mustache", "src\Tingle.Extensions.Mustache\Tingle.Extensions.Mustache.csproj", "{1CA15B17-1268-40B6-9939-7B500CD8AC3E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.PhoneValidators", "src\Tingle.Extensions.PhoneValidators\Tingle.Extensions.PhoneValidators.csproj", "{F46ADD04-B716-4E9B-9799-7C47DDDB08FC}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.Primitives", "src\Tingle.Extensions.Primitives\Tingle.Extensions.Primitives.csproj", "{3012AF4E-270B-4293-9C7B-51003F00A8D6}"
Expand Down Expand Up @@ -79,6 +81,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.JsonPatch
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.MongoDB.Tests", "tests\Tingle.Extensions.MongoDB.Tests\Tingle.Extensions.MongoDB.Tests.csproj", "{D8BFE67A-C3A7-4523-8119-D9FCD5EF3E7F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.Mustache.Tests", "tests\Tingle.Extensions.Mustache.Tests\Tingle.Extensions.Mustache.Tests.csproj", "{63EE1374-73A0-46AB-B879-FC8A586BF50B}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.PhoneValidators.Tests", "tests\Tingle.Extensions.PhoneValidators.Tests\Tingle.Extensions.PhoneValidators.Tests.csproj", "{526B37AD-256A-445A-9A42-E5C53989B11E}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tingle.Extensions.Primitives.Tests", "tests\Tingle.Extensions.Primitives.Tests\Tingle.Extensions.Primitives.Tests.csproj", "{33E2AC82-DC46-42CA-A41B-0D8D97019402}"
Expand Down Expand Up @@ -178,6 +182,10 @@ Global
{17F58DAD-0789-4AD7-921E-24CEA09DC68B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{17F58DAD-0789-4AD7-921E-24CEA09DC68B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{17F58DAD-0789-4AD7-921E-24CEA09DC68B}.Release|Any CPU.Build.0 = Release|Any CPU
{1CA15B17-1268-40B6-9939-7B500CD8AC3E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{1CA15B17-1268-40B6-9939-7B500CD8AC3E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{1CA15B17-1268-40B6-9939-7B500CD8AC3E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{1CA15B17-1268-40B6-9939-7B500CD8AC3E}.Release|Any CPU.Build.0 = Release|Any CPU
{F46ADD04-B716-4E9B-9799-7C47DDDB08FC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{F46ADD04-B716-4E9B-9799-7C47DDDB08FC}.Debug|Any CPU.Build.0 = Debug|Any CPU
{F46ADD04-B716-4E9B-9799-7C47DDDB08FC}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -254,6 +262,10 @@ Global
{D8BFE67A-C3A7-4523-8119-D9FCD5EF3E7F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8BFE67A-C3A7-4523-8119-D9FCD5EF3E7F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8BFE67A-C3A7-4523-8119-D9FCD5EF3E7F}.Release|Any CPU.Build.0 = Release|Any CPU
{63EE1374-73A0-46AB-B879-FC8A586BF50B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{63EE1374-73A0-46AB-B879-FC8A586BF50B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{63EE1374-73A0-46AB-B879-FC8A586BF50B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{63EE1374-73A0-46AB-B879-FC8A586BF50B}.Release|Any CPU.Build.0 = Release|Any CPU
{526B37AD-256A-445A-9A42-E5C53989B11E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{526B37AD-256A-445A-9A42-E5C53989B11E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{526B37AD-256A-445A-9A42-E5C53989B11E}.Release|Any CPU.ActiveCfg = Release|Any CPU
Expand Down Expand Up @@ -333,6 +345,7 @@ Global
{47F95938-964A-47FE-A0D6-1EDD0893455B} = {9546186D-D4E1-4EDB-956E-1F81C7F4DB72}
{913C0212-58AC-42B7-B555-F96B8E287E7F} = {9546186D-D4E1-4EDB-956E-1F81C7F4DB72}
{17F58DAD-0789-4AD7-921E-24CEA09DC68B} = {9546186D-D4E1-4EDB-956E-1F81C7F4DB72}
{1CA15B17-1268-40B6-9939-7B500CD8AC3E} = {9546186D-D4E1-4EDB-956E-1F81C7F4DB72}
{F46ADD04-B716-4E9B-9799-7C47DDDB08FC} = {9546186D-D4E1-4EDB-956E-1F81C7F4DB72}
{3012AF4E-270B-4293-9C7B-51003F00A8D6} = {9546186D-D4E1-4EDB-956E-1F81C7F4DB72}
{A803DE4B-B050-48F2-82A1-8E947D8FB96C} = {9546186D-D4E1-4EDB-956E-1F81C7F4DB72}
Expand All @@ -352,6 +365,7 @@ Global
{D0C66D3A-ED1F-486E-AA19-BDBB19025368} = {815F0941-3B70-4705-A583-AF627559595C}
{B82E2980-E145-4341-BAE0-8FAE1F110D0C} = {815F0941-3B70-4705-A583-AF627559595C}
{D8BFE67A-C3A7-4523-8119-D9FCD5EF3E7F} = {815F0941-3B70-4705-A583-AF627559595C}
{63EE1374-73A0-46AB-B879-FC8A586BF50B} = {815F0941-3B70-4705-A583-AF627559595C}
{526B37AD-256A-445A-9A42-E5C53989B11E} = {815F0941-3B70-4705-A583-AF627559595C}
{33E2AC82-DC46-42CA-A41B-0D8D97019402} = {815F0941-3B70-4705-A583-AF627559595C}
{978023EA-2ED5-4A28-96AD-4BB914EF2BE5} = {815F0941-3B70-4705-A583-AF627559595C}
Expand Down
49 changes: 49 additions & 0 deletions src/Tingle.Extensions.Mustache/Contexts/BaseTraversalContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using System.Text.RegularExpressions;

namespace Tingle.Extensions.Mustache.Contexts;

/// <param name="key">The key for access.</param>
public abstract partial class BaseTraversalContext<T>(string key) : ITraversalContext where T : ITraversalContext
{
/// <summary>
///
/// </summary>
/// <param name="key">The key for access.</param>
/// <param name="parent">The owning context.</param>
public BaseTraversalContext(string key, T parent) : this(key)
{
Parent = parent;
}

/// <inheritdoc/>
public string Key { get; } = key;

/// <summary>
/// The owning context.
/// </summary>
public T? Parent { get; }

///
protected abstract T GetContextForPath(Queue<string> elements, bool ignoreCase);

///
protected internal virtual T GetContextForPath(string path, bool ignoreCase)
{
if (string.IsNullOrWhiteSpace(path))
{
throw new ArgumentException($"'{nameof(path)}' cannot be null or whitespace.", nameof(path));
}

var elements = new Queue<string>();
var matches = GetPathFinderFormat().Matches(path).OfType<Match>().ToList();
foreach (var m in matches)
{
elements.Enqueue(m.Value);
}

return GetContextForPath(elements, ignoreCase);
}

[GeneratedRegex("(\\.\\.[\\\\/]{1})|([^.]+)", RegexOptions.IgnoreCase | RegexOptions.Compiled | RegexOptions.Singleline, "en-KE")]
private static partial Regex GetPathFinderFormat();
}
10 changes: 10 additions & 0 deletions src/Tingle.Extensions.Mustache/Contexts/ITraversalContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace Tingle.Extensions.Mustache.Contexts;

///
public interface ITraversalContext
{
/// <summary>
/// The key for access.
/// </summary>
string Key { get; }
}
13 changes: 13 additions & 0 deletions src/Tingle.Extensions.Mustache/Contexts/InferredUsage.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
namespace Tingle.Extensions.Mustache.Contexts;

/// <summary>
/// Allows us to capture how each path is used in an inferred template.
/// </summary>
public enum InferredUsage
{
#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member
Scalar,
ConditionalValue,
Collection,
#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member
}
95 changes: 95 additions & 0 deletions src/Tingle.Extensions.Mustache/Contexts/InferredValuesContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
namespace Tingle.Extensions.Mustache.Contexts;

///
public class InferredValuesContext : BaseTraversalContext<InferredValuesContext>
{
///
public InferredValuesContext(string key) : base(key) { }

///
public InferredValuesContext(string key, InferredValuesContext parent) : base(key, parent) { }

/// <summary>Usages for this context.</summary>
public ICollection<InferredUsage> Usages { get; } = new HashSet<InferredUsage>();

/// <summary>Children owned by this context.</summary>
private Dictionary<string, InferredValuesContext> Children { get; } = [];

/// <inheritdoc/>
protected override InferredValuesContext GetContextForPath(Queue<string> elements, bool ignoreCase)
{
ArgumentNullException.ThrowIfNull(elements);
if (elements.Count == 0) return this;

var element = elements.Dequeue();
if (element.StartsWith(".."))
{
if (Parent is not null)
{
return Parent.GetContextForPath(elements, ignoreCase);
}
else
{
// calling "../" too much may be okay in that if we're at root,
// we may just stop recursion and traverse down the path.
return GetContextForPath(elements, ignoreCase);
}
}
// TODO: handle array accessor and maybe "special" keys
else
{
// always return the context, even if the value is null
if (!Children.TryGetValue(element, out var inner))
{
inner = new InferredValuesContext(key: element, parent: this);
Children[element] = inner;
}
return inner.GetContextForPath(elements, ignoreCase);
}
}

///
public InferredValuesContext GetInferredContextForPath(string path, InferredUsage accessType, bool ignoreCase)
{
var context = GetContextForPath(path, ignoreCase);
context.Usages.Add(accessType);
return context;
}

/// <summary>
/// Returns an <see cref="object"/> containing the current inferred model representation.
/// </summary>
/// <returns></returns>
public object ToModel()
{
object result;
if (Usages.Count == 0)
{
result = Children.ToDictionary(k => k.Key, v => v.Value.ToModel());
}
else if (Usages.Contains(InferredUsage.Scalar) && Usages.Count == 1)
{
result = Key + "_Value";
}
else
{
if (Usages.Contains(InferredUsage.Collection))
{
if (Children.Count != 0)
{
result = new[] { Children.ToDictionary(k => k.Key, v => v.Value.ToModel()) };
}
else
{
result = Enumerable.Range(1, 3).Select(k => Key + "_" + k).ToArray();
}
}
else
{
result = Children.ToDictionary(k => k.Key, v => v.Value.ToModel());
}
}

return result;
}
}
99 changes: 99 additions & 0 deletions src/Tingle.Extensions.Mustache/Contexts/ProvidedValuesContext.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
using System.Reflection;

namespace Tingle.Extensions.Mustache.Contexts;

/// <summary>
///
/// </summary>
public class ProvidedValuesContext : BaseTraversalContext<ProvidedValuesContext>
{
/// <summary>
///
/// </summary>
/// <param name="key">The key for access.</param>
/// <param name="value">The value associated with this context.</param>
public ProvidedValuesContext(string key, object value) : base(key)
{
Value = value;
}

/// <summary>
///
/// </summary>
/// <param name="key">The key for access.</param>
/// <param name="value">The value associated with this context.</param>
/// <param name="parent">The owning context.</param>
public ProvidedValuesContext(string key, object value, ProvidedValuesContext parent) : base(key, parent)
{
Value = value;
}

/// <summary>
///
/// </summary>
/// <param name="key">The key for access.</param>
/// <param name="parent">The owning context.</param>
public ProvidedValuesContext(string key, ProvidedValuesContext parent) : base(key, parent) { }

/// <summary>
/// The value associated with this context.
/// </summary>
public object? Value { get; set; }

/// <summary>Determines if the value of this context exists.</summary>
public bool Exists() => Value is not null;

/// <inheritdoc/>
protected override ProvidedValuesContext GetContextForPath(Queue<string> elements, bool ignoreCase)
{
ArgumentNullException.ThrowIfNull(elements);
if (elements.Count == 0) return this;

var element = elements.Dequeue();
if (element.StartsWith(".."))
{
if (Parent is not null)
{
return Parent.GetContextForPath(elements, ignoreCase);
}
else
{
// calling "../" too much may be okay in that if we're at root,
// we may just stop recursion and traverse down the path.
return GetContextForPath(elements, ignoreCase);
}
}
// TODO: handle array accessor and maybe "special" keys
else
{
// always return the context, even if the value is null
ProvidedValuesContext? inner = null;
if (Value is IDictionary<string, object> ctx)
{
if (ignoreCase)
{
ctx = new Dictionary<string, object>(ctx, StringComparer.OrdinalIgnoreCase);
}

ctx.TryGetValue(element, out var innerV);
inner = new ProvidedValuesContext(key: element, value: innerV!, parent: this);
}
else if (Value is not null)
{
var type = Value.GetType();
var flags = BindingFlags.Public | BindingFlags.Static | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
if (ignoreCase) flags |= BindingFlags.IgnoreCase;
var prop = type.GetProperty(element, flags);
if (prop is not null)
{
var innerV = prop.GetValue(Value);
inner = new ProvidedValuesContext(key: element, value: innerV!, parent: this);
}
}

//return inner ?? new ProvidedValuesContext(key: element, parent: this);
inner ??= new ProvidedValuesContext(key: element, parent: this);
return inner.GetContextForPath(elements, ignoreCase);
}
}
}
Loading

0 comments on commit 61fb3e9

Please sign in to comment.