Skip to content

Commit 4c882d6

Browse files
authored
Merge pull request #11 from serilog/alignment-specifiers
Implement alignment specifiers in templates, fixes #7
2 parents a8d5417 + 92f6e42 commit 4c882d6

File tree

8 files changed

+131
-5
lines changed

8 files changed

+131
-5
lines changed
Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
using System;
22
using Serilog.Expressions.Ast;
3+
using Serilog.Parsing;
34

45
namespace Serilog.Templates.Ast
56
{
67
class FormattedExpression : Template
78
{
89
public Expression Expression { get; }
910
public string? Format { get; }
11+
public Alignment? Alignment { get; }
1012

11-
public FormattedExpression(Expression expression, string? format)
13+
public FormattedExpression(Expression expression, string? format, Alignment? alignment)
1214
{
1315
Expression = expression ?? throw new ArgumentNullException(nameof(expression));
1416
Format = format;
17+
Alignment = alignment;
1518
}
1619
}
17-
}
20+
}

src/Serilog.Expressions/Templates/Compilation/CompiledFormattedExpression.cs

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using Serilog.Events;
44
using Serilog.Expressions;
55
using Serilog.Formatting.Json;
6+
using Serilog.Parsing;
67
using Serilog.Templates.Rendering;
78

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

1415
readonly CompiledExpression _expression;
1516
readonly string? _format;
17+
readonly Alignment? _alignment;
1618

17-
public CompiledFormattedExpression(CompiledExpression expression, string? format)
19+
public CompiledFormattedExpression(CompiledExpression expression, string? format, Alignment? alignment)
1820
{
1921
_expression = expression ?? throw new ArgumentNullException(nameof(expression));
2022
_format = format;
23+
_alignment = alignment;
2124
}
2225

2326
public override void Evaluate(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider)
27+
{
28+
if (_alignment == null)
29+
{
30+
EvaluateUnaligned(logEvent, output, formatProvider);
31+
}
32+
else
33+
{
34+
var writer = new StringWriter();
35+
EvaluateUnaligned(logEvent, writer, formatProvider);
36+
Padding.Apply(output, writer.ToString(), _alignment.Value);
37+
}
38+
}
39+
40+
void EvaluateUnaligned(LogEvent logEvent, TextWriter output, IFormatProvider? formatProvider)
2441
{
2542
var value = _expression(logEvent);
2643
if (value == null)

src/Serilog.Expressions/Templates/Compilation/TemplateCompiler.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public static CompiledTemplate Compile(Template template, NameResolver nameResol
1515
{
1616
LiteralText text => new CompiledLiteralText(text.Text),
1717
FormattedExpression expression => new CompiledFormattedExpression(
18-
ExpressionCompiler.Compile(expression.Expression, nameResolver), expression.Format),
18+
ExpressionCompiler.Compile(expression.Expression, nameResolver), expression.Format, expression.Alignment),
1919
TemplateBlock block => new CompiledTemplateBlock(block.Elements.Select(e => Compile(e, nameResolver)).ToArray()),
2020
_ => throw new NotSupportedException()
2121
};

src/Serilog.Expressions/Templates/Parsing/TemplateParser.cs

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
using System;
22
using System.Collections.Generic;
33
using System.Diagnostics.CodeAnalysis;
4+
using System.Globalization;
45
using System.Linq;
56
using System.Text;
67
using Serilog.Expressions.Parsing;
78
using Serilog.Templates.Ast;
9+
using Serilog.Parsing;
810
using Superpower.Model;
911

1012
namespace Serilog.Templates.Parsing
@@ -65,6 +67,48 @@ public static bool TryParse(
6567
return false;
6668
}
6769

70+
Alignment? alignment = null;
71+
if (template[i] == ',')
72+
{
73+
i++;
74+
75+
if (i >= template.Length || template[i] == '}')
76+
{
77+
error = "Incomplete alignment specifier, expected width.";
78+
return false;
79+
}
80+
81+
AlignmentDirection direction;
82+
if (template[i] == '-')
83+
{
84+
direction = AlignmentDirection.Left;
85+
i++;
86+
87+
if (i >= template.Length || template[i] == '}')
88+
{
89+
error = "Incomplete alignment specifier, expected digits.";
90+
return false;
91+
}
92+
}
93+
else
94+
direction = AlignmentDirection.Right;
95+
96+
if (!char.IsDigit(template[i]))
97+
{
98+
error = "Invalid alignment specifier, expected digits.";
99+
return false;
100+
}
101+
102+
var width = 0;
103+
while (i < template.Length && char.IsDigit(template[i]))
104+
{
105+
width = 10 * width + CharUnicodeInfo.GetDecimalDigitValue(template[i]);
106+
i++;
107+
}
108+
109+
alignment = new Alignment(direction, width);
110+
}
111+
68112
string? format = null;
69113
if (template[i] == ':')
70114
{
@@ -94,7 +138,7 @@ public static bool TryParse(
94138

95139
i++;
96140

97-
elements.Add(new FormattedExpression(expr.Value, format));
141+
elements.Add(new FormattedExpression(expr.Value, format, alignment));
98142
}
99143
}
100144
else if (ch == '}')

src/Serilog.Expressions/Templates/Rendering/LevelRenderer.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414

1515
using Serilog.Events;
1616

17+
// ReSharper disable StringLiteralTypo
18+
1719
namespace Serilog.Templates.Rendering
1820
{
1921
/// <summary>
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// Copyright 2013-2020 Serilog Contributors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
using System.IO;
16+
using System.Linq;
17+
using Serilog.Parsing;
18+
19+
namespace Serilog.Templates.Rendering
20+
{
21+
static class Padding
22+
{
23+
static readonly char[] PaddingChars = Enumerable.Repeat(' ', 80).ToArray();
24+
25+
/// <summary>
26+
/// Writes the provided value to the output, applying direction-based padding when <paramref name="alignment"/> is provided.
27+
/// </summary>
28+
public static void Apply(TextWriter output, string value, Alignment alignment)
29+
{
30+
if (value.Length >= alignment.Width)
31+
{
32+
output.Write(value);
33+
return;
34+
}
35+
36+
var pad = alignment.Width - value.Length;
37+
38+
if (alignment.Direction == AlignmentDirection.Left)
39+
output.Write(value);
40+
41+
if (pad <= PaddingChars.Length)
42+
{
43+
output.Write(PaddingChars, 0, pad);
44+
}
45+
else
46+
{
47+
output.Write(new string(' ', pad));
48+
}
49+
50+
if (alignment.Direction == AlignmentDirection.Right)
51+
output.Write(value);
52+
}
53+
}
54+
}

test/Serilog.Expressions.Tests/Cases/template-evaluation-cases.asv

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,7 @@ Text only ⇶ Text only
99
{{ Escaped {{ left {{ ⇶ { Escaped { left {
1010
}} Escaped }} right }} ⇶ } Escaped } right }
1111
Formatted {42:0000} ⇶ Formatted 0042
12+
Aligned {42,4}! ⇶ Aligned 42!
13+
Left {42,-4}! ⇶ Left 42 !
14+
Under width {42,0}! ⇶ Under width 42!
1215
{@m} ⇶ Hello, nblumhardt!

test/Serilog.Expressions.Tests/TemplateParserTests.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ public class TemplateParserTests
1212
[InlineData("Unclosed {hole", "Un-closed hole, `}` expected.")]
1313
[InlineData("Syntax {+Err}or", "Invalid expression, unexpected operator `+`, expected expression.")]
1414
[InlineData("Syntax {1 + 2 and}or", "Invalid expression, unexpected `}`, expected expression.")]
15+
[InlineData("Missing {Align,-} digits", "Incomplete alignment specifier, expected digits.")]
16+
[InlineData("Non-digit {Align,x} specifier", "Invalid alignment specifier, expected digits.")]
17+
[InlineData("Empty {Align,} digits", "Incomplete alignment specifier, expected width.")]
1518
public void ErrorsAreReported(string input, string error)
1619
{
1720
Assert.False(ExpressionTemplate.TryParse(input, null, null, out _, out var actual));

0 commit comments

Comments
 (0)