feat: add support for template variants

This commit is contained in:
Oliver Booth 2023-08-15 17:04:43 +01:00
parent 1cdad4c17c
commit 7ee9d3637c
Signed by: oliverbooth
GPG Key ID: B89D139977693FED
7 changed files with 92 additions and 15 deletions

View File

@ -11,9 +11,10 @@ internal sealed class TemplateConfiguration : IEntityTypeConfiguration<Template>
public void Configure(EntityTypeBuilder<Template> builder)
{
builder.ToTable("Template");
builder.HasKey(e => e.Name);
builder.HasKey(e => new { e.Name, e.Variant });
builder.Property(e => e.Name).IsRequired();
builder.Property(e => e.Name).HasMaxLength(50).IsRequired();
builder.Property(e => e.Variant).HasMaxLength(50).IsRequired();
builder.Property(e => e.FormatString).IsRequired();
}
}

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data;
namespace OliverBooth.Data.Web;
/// <summary>
/// Represents a template.
@ -15,4 +15,10 @@ public interface ITemplate
/// Gets the name of the template.
/// </summary>
string Name { get; }
/// <summary>
/// Gets the variant of the template.
/// </summary>
/// <value>The variant of the template.</value>
string Variant { get; }
}

View File

@ -11,6 +11,9 @@ public sealed class Template : ITemplate, IEquatable<Template>
/// <inheritdoc />
public string Name { get; private set; } = string.Empty;
/// <inheritdoc />
public string Variant { get; private set; } = string.Empty;
/// <summary>
/// Returns a value indicating whether two instances of <see cref="Template" /> are equal.
/// </summary>
@ -46,7 +49,7 @@ public sealed class Template : ITemplate, IEquatable<Template>
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Name == other.Name;
return Name == other.Name && Variant == other.Variant;
}
/// <summary>
@ -68,7 +71,7 @@ public sealed class Template : ITemplate, IEquatable<Template>
/// <returns>The hash code.</returns>
public override int GetHashCode()
{
// ReSharper disable once NonReadonlyMemberInGetHashCode
return Name.GetHashCode();
// ReSharper disable NonReadonlyMemberInGetHashCode
return HashCode.Combine(Name, Variant);
}
}

View File

@ -30,4 +30,10 @@ public sealed class TemplateInline : Inline
/// </summary>
/// <value>The template parameters.</value>
public IReadOnlyDictionary<string, string> Params { get; set; } = null!;
/// <summary>
/// Gets the variant of the template.
/// </summary>
/// <value>The variant of the template.</value>
public string Variant { get; set; } = string.Empty;
}

View File

@ -1,6 +1,7 @@
using Cysharp.Text;
using Markdig.Helpers;
using Markdig.Parsers;
using Serilog;
namespace OliverBooth.Markdown.Template;
@ -37,11 +38,23 @@ public sealed class TemplateInlineParser : InlineParser
template = template[2..^2]; // trim {{ and }}
ReadOnlySpan<char> name = ReadTemplateName(template, out ReadOnlySpan<char> argumentSpan);
int variantIndex = name.IndexOf(':');
bool hasVariant = variantIndex > -1;
var variant = ReadOnlySpan<char>.Empty;
if (hasVariant)
{
Log.Debug("Index of variant: {Index}", variantIndex);
variant = name[(variantIndex + 1)..];
name = name[..variantIndex];
}
if (argumentSpan.IsEmpty)
{
processor.Inline = new TemplateInline
{
Name = name.ToString(),
Variant = hasVariant ? variant.ToString() : string.Empty,
ArgumentString = string.Empty,
ArgumentList = ArraySegment<string>.Empty,
Params = EmptyParams
@ -60,6 +73,7 @@ public sealed class TemplateInlineParser : InlineParser
processor.Inline = new TemplateInline
{
Name = name.ToString(),
Variant = hasVariant ? variant.ToString() : string.Empty,
ArgumentString = argumentSpan.ToString(),
ArgumentList = argumentList.AsReadOnly(),
Params = paramsList.AsReadOnly()

View File

@ -1,4 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using OliverBooth.Data;
using OliverBooth.Data.Web;
using OliverBooth.Markdown.Template;
namespace OliverBooth.Services;
@ -28,4 +30,27 @@ public interface ITemplateService
/// <paramref name="templateInline" /> is <see langword="null" />.
/// </exception>
string RenderTemplate(TemplateInline templateInline, ITemplate? template);
/// <summary>
/// Attempts to get the template with the specified name.
/// </summary>
/// <param name="name">The name of the template.</param>
/// <param name="template">
/// When this method returns, contains the template with the specified name, if the template is found;
/// otherwise, <see langword="null" />.
/// </param>
/// <returns><see langword="true" /> if the template exists; otherwise, <see langword="false" />.</returns>
bool TryGetTemplate(string name, [NotNullWhen(true)] out ITemplate? template);
/// <summary>
/// Attempts to get the template with the specified name and variant.
/// </summary>
/// <param name="name">The name of the template.</param>
/// <param name="variant">The variant of the template.</param>
/// <param name="template">
/// When this method returns, contains the template with the specified name and variant, if the template is
/// found; otherwise, <see langword="null" />.
/// </param>
/// <returns><see langword="true" /> if the template exists; otherwise, <see langword="false" />.</returns>
bool TryGetTemplate(string name, string variant, [NotNullWhen(true)] out ITemplate? template);
}

View File

@ -1,4 +1,5 @@
using System.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore;
using OliverBooth.Data;
using OliverBooth.Data.Web;
@ -40,17 +41,17 @@ internal sealed class TemplateService : ITemplateService
{
if (templateInline is null) throw new ArgumentNullException(nameof(templateInline));
using WebContext context = _webContextFactory.CreateDbContext();
Template? template = context.Templates.Find(templateInline.Name);
return RenderTemplate(templateInline, template);
return TryGetTemplate(templateInline.Name, templateInline.Variant, out ITemplate? template)
? RenderTemplate(templateInline, template)
: GetDefaultRender(templateInline);
}
/// <inheritdoc />
public string RenderTemplate(TemplateInline inline, ITemplate? template)
public string RenderTemplate(TemplateInline templateInline, ITemplate? template)
{
if (template is null)
{
return $"{{{{{inline.Name}}}}}";
return GetDefaultRender(templateInline);
}
Span<byte> randomBytes = stackalloc byte[20];
@ -58,9 +59,9 @@ internal sealed class TemplateService : ITemplateService
var formatted = new
{
inline.ArgumentList,
inline.ArgumentString,
inline.Params,
templateInline.ArgumentList,
templateInline.ArgumentString,
templateInline.Params,
RandomInt = BinaryPrimitives.ReadInt32LittleEndian(randomBytes[..4]),
RandomGuid = new Guid(randomBytes[4..]).ToString("N"),
};
@ -71,7 +72,28 @@ internal sealed class TemplateService : ITemplateService
}
catch
{
return $"{{{{{inline.Name}|{inline.ArgumentString}}}}}";
return GetDefaultRender(templateInline);
}
}
/// <inheritdoc />
public bool TryGetTemplate(string name, [NotNullWhen(true)] out ITemplate? template)
{
return TryGetTemplate(name, string.Empty, out template);
}
/// <inheritdoc />
public bool TryGetTemplate(string name, string variant, [NotNullWhen(true)] out ITemplate? template)
{
using WebContext context = _webContextFactory.CreateDbContext();
template = context.Templates.FirstOrDefault(t => t.Name == name && t.Variant == variant);
return template is not null;
}
private static string GetDefaultRender(TemplateInline templateInline)
{
return string.IsNullOrWhiteSpace(templateInline.ArgumentString)
? $"{{{{{templateInline.Name}}}}}"
: $"{{{{{templateInline.Name}|{templateInline.ArgumentString}}}}}";
}
}