Skip to content

Implement alignment specifiers in templates, fixes #7 #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions src/Serilog.Expressions/Templates/Ast/FormattedExpression.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,20 @@
using System;
using Serilog.Expressions.Ast;
using Serilog.Parsing;

namespace Serilog.Templates.Ast
{
class FormattedExpression : Template
{
public Expression Expression { get; }
public string? Format { get; }
public Alignment? Alignment { get; }

public FormattedExpression(Expression expression, string? format)
public FormattedExpression(Expression expression, string? format, Alignment? alignment)
{
Expression = expression ?? throw new ArgumentNullException(nameof(expression));
Format = format;
Alignment = alignment;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using Serilog.Events;
using Serilog.Expressions;
using Serilog.Formatting.Json;
using Serilog.Parsing;
using Serilog.Templates.Rendering;

namespace Serilog.Templates.Compilation
Expand All @@ -13,14 +14,30 @@ class CompiledFormattedExpression : CompiledTemplate

readonly CompiledExpression _expression;
readonly string? _format;
readonly Alignment? _alignment;

public CompiledFormattedExpression(CompiledExpression expression, string? format)
public CompiledFormattedExpression(CompiledExpression expression, string? format, Alignment? alignment)
{
_expression = expression ?? throw new ArgumentNullException(nameof(expression));
_format = format;
_alignment = alignment;
}

public override void Evaluate(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider)
{
if (_alignment == null)
{
EvaluateUnaligned(logEvent, output, formatProvider);
}
else
{
var writer = new StringWriter();
EvaluateUnaligned(logEvent, writer, formatProvider);
Padding.Apply(output, writer.ToString(), _alignment.Value);
}
}

void EvaluateUnaligned(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider)
{
var value = _expression(logEvent);
if (value == null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public static CompiledTemplate Compile(Template template, NameResolver nameResol
{
LiteralText text => new CompiledLiteralText(text.Text),
FormattedExpression expression => new CompiledFormattedExpression(
ExpressionCompiler.Compile(expression.Expression, nameResolver), expression.Format),
ExpressionCompiler.Compile(expression.Expression, nameResolver), expression.Format, expression.Alignment),
TemplateBlock block => new CompiledTemplateBlock(block.Elements.Select(e => Compile(e, nameResolver)).ToArray()),
_ => throw new NotSupportedException()
};
Expand Down
46 changes: 45 additions & 1 deletion src/Serilog.Expressions/Templates/Parsing/TemplateParser.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
using System.Linq;
using System.Text;
using Serilog.Expressions.Parsing;
using Serilog.Templates.Ast;
using Serilog.Parsing;
using Superpower.Model;

namespace Serilog.Templates.Parsing
Expand Down Expand Up @@ -65,6 +67,48 @@ public static bool TryParse(
return false;
}

Alignment? alignment = null;
if (template[i] == ',')
{
i++;

if (i >= template.Length || template[i] == '}')
{
error = "Incomplete alignment specifier, expected width.";
return false;
}

AlignmentDirection direction;
if (template[i] == '-')
{
direction = AlignmentDirection.Left;
i++;

if (i >= template.Length || template[i] == '}')
{
error = "Incomplete alignment specifier, expected digits.";
return false;
}
}
else
direction = AlignmentDirection.Right;

if (!char.IsDigit(template[i]))
{
error = "Invalid alignment specifier, expected digits.";
return false;
}

var width = 0;
while (i < template.Length && char.IsDigit(template[i]))
{
width = 10 * width + CharUnicodeInfo.GetDecimalDigitValue(template[i]);
i++;
}

alignment = new Alignment(direction, width);
}

string? format = null;
if (template[i] == ':')
{
Expand Down Expand Up @@ -94,7 +138,7 @@ public static bool TryParse(

i++;

elements.Add(new FormattedExpression(expr.Value, format));
elements.Add(new FormattedExpression(expr.Value, format, alignment));
}
}
else if (ch == '}')
Expand Down
2 changes: 2 additions & 0 deletions src/Serilog.Expressions/Templates/Rendering/LevelRenderer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@

using Serilog.Events;

// ReSharper disable StringLiteralTypo

namespace Serilog.Templates.Rendering
{
/// <summary>
Expand Down
54 changes: 54 additions & 0 deletions src/Serilog.Expressions/Templates/Rendering/Padding.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
// Copyright 2013-2020 Serilog Contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using System.IO;
using System.Linq;
using Serilog.Parsing;

namespace Serilog.Templates.Rendering
{
static class Padding
{
static readonly char[] PaddingChars = Enumerable.Repeat(' ', 80).ToArray();

/// <summary>
/// Writes the provided value to the output, applying direction-based padding when <paramref name="alignment"/> is provided.
/// </summary>
public static void Apply(TextWriter output, string value, Alignment alignment)
{
if (value.Length >= alignment.Width)
{
output.Write(value);
return;
}

var pad = alignment.Width - value.Length;

if (alignment.Direction == AlignmentDirection.Left)
output.Write(value);

if (pad <= PaddingChars.Length)
{
output.Write(PaddingChars, 0, pad);
}
else
{
output.Write(new string(' ', pad));
}

if (alignment.Direction == AlignmentDirection.Right)
output.Write(value);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ Text only ⇶ Text only
{{ Escaped {{ left {{ ⇶ { Escaped { left {
}} Escaped }} right }} ⇶ } Escaped } right }
Formatted {42:0000} ⇶ Formatted 0042
Aligned {42,4}! ⇶ Aligned 42!
Left {42,-4}! ⇶ Left 42 !
Under width {42,0}! ⇶ Under width 42!
{@m} ⇶ Hello, nblumhardt!
3 changes: 3 additions & 0 deletions test/Serilog.Expressions.Tests/TemplateParserTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public class TemplateParserTests
[InlineData("Unclosed {hole", "Un-closed hole, `}` expected.")]
[InlineData("Syntax {+Err}or", "Invalid expression, unexpected operator `+`, expected expression.")]
[InlineData("Syntax {1 + 2 and}or", "Invalid expression, unexpected `}`, expected expression.")]
[InlineData("Missing {Align,-} digits", "Incomplete alignment specifier, expected digits.")]
[InlineData("Non-digit {Align,x} specifier", "Invalid alignment specifier, expected digits.")]
[InlineData("Empty {Align,} digits", "Incomplete alignment specifier, expected width.")]
public void ErrorsAreReported(string input, string error)
{
Assert.False(ExpressionTemplate.TryParse(input, null, null, out _, out var actual));
Expand Down