diff --git a/src/SeqCli/Cli/Commands/PrintCommand.cs b/src/SeqCli/Cli/Commands/PrintCommand.cs index 505e1a10..2740297f 100644 --- a/src/SeqCli/Cli/Commands/PrintCommand.cs +++ b/src/SeqCli/Cli/Commands/PrintCommand.cs @@ -20,6 +20,7 @@ using SeqCli.Cli.Features; using SeqCli.Config; using SeqCli.Ingestion; +using SeqCli.Output; using SeqCli.Util; using Serilog; using Serilog.Events; @@ -72,7 +73,8 @@ protected override async Task Run() filter = evt => ExpressionResult.IsTrue(compiled(evt)); } - var output = _output.GetOutputFormat(config, _template); + var template = _template == null ? null : PrintTemplate.InterpretEscapeChars(_template); + var output = _output.GetOutputFormat(config, template); foreach (var input in _fileInputFeature.OpenInputs()) { diff --git a/src/SeqCli/Output/PrintTemplate.cs b/src/SeqCli/Output/PrintTemplate.cs new file mode 100644 index 00000000..55c37f82 --- /dev/null +++ b/src/SeqCli/Output/PrintTemplate.cs @@ -0,0 +1,59 @@ +// Copyright © Datalust Pty Ltd +// +// 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; +using System.Text; + +namespace SeqCli.Output; + +static class PrintTemplate +{ + /// + /// Windows disallows passing literal newlines in process arguments (and it's inconvenient to figure out + /// how to do it with varied shells, anyway). This function provides JSON-like (C#-like) backslash-escaped + /// substitutes for newlines and a few other nonprintable characters. + /// + public static string InterpretEscapeChars(string template) + { + var result = new StringBuilder(); + for (var i = 0; i < template.Length; ++i) + { + var ch = template[i]; + if (ch != '\\') + { + result.Append(ch); + continue; + } + + if (i == template.Length - 1) + throw new ArgumentException(@"Trailing literal `\` is invalid in templates and must be escaped as `\\`."); + + i += 1; + var next = template[i]; + result.Append(next switch + { + '\\' => '\\', + 't' => '\t', + 'r' => '\r', + 'n' => '\n', + 'e' => '\e', + 'b' => '\b', + 'f' => '\f', + _ => throw new ArgumentException($@"Sequence `\{next}` is invalid in templates; escape literal `\` as `\\`.") + }); + } + + return result.ToString(); + } +} \ No newline at end of file diff --git a/test/SeqCli.Tests/Output/PrintTemplateTests.cs b/test/SeqCli.Tests/Output/PrintTemplateTests.cs new file mode 100644 index 00000000..b6a96765 --- /dev/null +++ b/test/SeqCli.Tests/Output/PrintTemplateTests.cs @@ -0,0 +1,30 @@ +using System; +using SeqCli.Output; +using Xunit; + +namespace SeqCli.Tests.Output; + +public class PrintTemplateTests +{ + [Theory] + [InlineData("", "")] + [InlineData("abc", "abc")] + [InlineData(@"{@m}\\{@x}", @"{@m}\{@x}")] + [InlineData(@"{@m}\n{@x}", "{@m}\n{@x}")] + [InlineData(@"{@m}\r\n{@x}", "{@m}\r\n{@x}")] + [InlineData(@"\\\n", "\\\n")] + public void PrintTemplatesAreProcessedCorrectly(string template, string expected) + { + var actual = PrintTemplate.InterpretEscapeChars(template); + Assert.Equal(expected, actual); + } + + [Theory] + [InlineData(@"\")] + [InlineData(@"test\")] + [InlineData(@"\q")] + public void InvalidTemplatesAreRejected(string template) + { + Assert.Throws(() => PrintTemplate.InterpretEscapeChars(template)); + } +} \ No newline at end of file