Compare commits

...

4 Commits

9 changed files with 94 additions and 16 deletions

View File

@ -11,9 +11,10 @@ internal sealed class TemplateConfiguration : IEntityTypeConfiguration<Template>
public void Configure(EntityTypeBuilder<Template> builder) public void Configure(EntityTypeBuilder<Template> builder)
{ {
builder.ToTable("Template"); 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(); builder.Property(e => e.FormatString).IsRequired();
} }
} }

View File

@ -1,4 +1,4 @@
namespace OliverBooth.Data; namespace OliverBooth.Data.Web;
/// <summary> /// <summary>
/// Represents a template. /// Represents a template.
@ -15,4 +15,10 @@ public interface ITemplate
/// Gets the name of the template. /// Gets the name of the template.
/// </summary> /// </summary>
string Name { get; } 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 /> /// <inheritdoc />
public string Name { get; private set; } = string.Empty; public string Name { get; private set; } = string.Empty;
/// <inheritdoc />
public string Variant { get; private set; } = string.Empty;
/// <summary> /// <summary>
/// Returns a value indicating whether two instances of <see cref="Template" /> are equal. /// Returns a value indicating whether two instances of <see cref="Template" /> are equal.
/// </summary> /// </summary>
@ -46,7 +49,7 @@ public sealed class Template : ITemplate, IEquatable<Template>
{ {
if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true; if (ReferenceEquals(this, other)) return true;
return Name == other.Name; return Name == other.Name && Variant == other.Variant;
} }
/// <summary> /// <summary>
@ -68,7 +71,7 @@ public sealed class Template : ITemplate, IEquatable<Template>
/// <returns>The hash code.</returns> /// <returns>The hash code.</returns>
public override int GetHashCode() public override int GetHashCode()
{ {
// ReSharper disable once NonReadonlyMemberInGetHashCode // ReSharper disable NonReadonlyMemberInGetHashCode
return Name.GetHashCode(); return HashCode.Combine(Name, Variant);
} }
} }

View File

@ -30,4 +30,10 @@ public sealed class TemplateInline : Inline
/// </summary> /// </summary>
/// <value>The template parameters.</value> /// <value>The template parameters.</value>
public IReadOnlyDictionary<string, string> Params { get; set; } = null!; 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

@ -37,11 +37,22 @@ public sealed class TemplateInlineParser : InlineParser
template = template[2..^2]; // trim {{ and }} template = template[2..^2]; // trim {{ and }}
ReadOnlySpan<char> name = ReadTemplateName(template, out ReadOnlySpan<char> argumentSpan); ReadOnlySpan<char> name = ReadTemplateName(template, out ReadOnlySpan<char> argumentSpan);
int variantIndex = name.IndexOf(':');
bool hasVariant = variantIndex > -1;
var variant = ReadOnlySpan<char>.Empty;
if (hasVariant)
{
variant = name[(variantIndex + 1)..];
name = name[..variantIndex];
}
if (argumentSpan.IsEmpty) if (argumentSpan.IsEmpty)
{ {
processor.Inline = new TemplateInline processor.Inline = new TemplateInline
{ {
Name = name.ToString(), Name = name.ToString(),
Variant = hasVariant ? variant.ToString() : string.Empty,
ArgumentString = string.Empty, ArgumentString = string.Empty,
ArgumentList = ArraySegment<string>.Empty, ArgumentList = ArraySegment<string>.Empty,
Params = EmptyParams Params = EmptyParams
@ -60,6 +71,7 @@ public sealed class TemplateInlineParser : InlineParser
processor.Inline = new TemplateInline processor.Inline = new TemplateInline
{ {
Name = name.ToString(), Name = name.ToString(),
Variant = hasVariant ? variant.ToString() : string.Empty,
ArgumentString = argumentSpan.ToString(), ArgumentString = argumentSpan.ToString(),
ArgumentList = argumentList.AsReadOnly(), ArgumentList = argumentList.AsReadOnly(),
Params = paramsList.AsReadOnly() Params = paramsList.AsReadOnly()

View File

@ -18,7 +18,7 @@
<nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb"> <nav style="--bs-breadcrumb-divider: '>';" aria-label="breadcrumb">
<ol class="breadcrumb"> <ol class="breadcrumb">
<li class="breadcrumb-item"> <li class="breadcrumb-item">
<a asp-page="/index">Blog</a> <a asp-page="index">Blog</a>
</li> </li>
<li class="breadcrumb-item active" aria-current="page">@post.Title</li> <li class="breadcrumb-item active" aria-current="page">@post.Title</li>
</ol> </ol>

View File

@ -10,6 +10,9 @@ using Serilog;
Log.Logger = new LoggerConfiguration() Log.Logger = new LoggerConfiguration()
.WriteTo.Console() .WriteTo.Console()
.WriteTo.File("logs/latest.log", rollingInterval: RollingInterval.Day) .WriteTo.File("logs/latest.log", rollingInterval: RollingInterval.Day)
#if DEBUG
.MinimumLevel.Debug()
#endif
.CreateLogger(); .CreateLogger();
WebApplicationBuilder builder = WebApplication.CreateBuilder(args); WebApplicationBuilder builder = WebApplication.CreateBuilder(args);

View File

@ -1,4 +1,6 @@
using System.Diagnostics.CodeAnalysis;
using OliverBooth.Data; using OliverBooth.Data;
using OliverBooth.Data.Web;
using OliverBooth.Markdown.Template; using OliverBooth.Markdown.Template;
namespace OliverBooth.Services; namespace OliverBooth.Services;
@ -28,4 +30,27 @@ public interface ITemplateService
/// <paramref name="templateInline" /> is <see langword="null" />. /// <paramref name="templateInline" /> is <see langword="null" />.
/// </exception> /// </exception>
string RenderTemplate(TemplateInline templateInline, ITemplate? template); 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.Buffers.Binary;
using System.Diagnostics.CodeAnalysis;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using OliverBooth.Data; using OliverBooth.Data;
using OliverBooth.Data.Web; using OliverBooth.Data.Web;
@ -40,17 +41,17 @@ internal sealed class TemplateService : ITemplateService
{ {
if (templateInline is null) throw new ArgumentNullException(nameof(templateInline)); if (templateInline is null) throw new ArgumentNullException(nameof(templateInline));
using WebContext context = _webContextFactory.CreateDbContext(); return TryGetTemplate(templateInline.Name, templateInline.Variant, out ITemplate? template)
Template? template = context.Templates.Find(templateInline.Name); ? RenderTemplate(templateInline, template)
return RenderTemplate(templateInline, template); : GetDefaultRender(templateInline);
} }
/// <inheritdoc /> /// <inheritdoc />
public string RenderTemplate(TemplateInline inline, ITemplate? template) public string RenderTemplate(TemplateInline templateInline, ITemplate? template)
{ {
if (template is null) if (template is null)
{ {
return $"{{{{{inline.Name}}}}}"; return GetDefaultRender(templateInline);
} }
Span<byte> randomBytes = stackalloc byte[20]; Span<byte> randomBytes = stackalloc byte[20];
@ -58,9 +59,9 @@ internal sealed class TemplateService : ITemplateService
var formatted = new var formatted = new
{ {
inline.ArgumentList, templateInline.ArgumentList,
inline.ArgumentString, templateInline.ArgumentString,
inline.Params, templateInline.Params,
RandomInt = BinaryPrimitives.ReadInt32LittleEndian(randomBytes[..4]), RandomInt = BinaryPrimitives.ReadInt32LittleEndian(randomBytes[..4]),
RandomGuid = new Guid(randomBytes[4..]).ToString("N"), RandomGuid = new Guid(randomBytes[4..]).ToString("N"),
}; };
@ -71,7 +72,28 @@ internal sealed class TemplateService : ITemplateService
} }
catch 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}}}}}";
}
} }