From 3f62e7e929ee7845dab7349553991b5042429620 Mon Sep 17 00:00:00 2001 From: Chris Cameron Date: Tue, 25 Feb 2020 19:05:40 -0500 Subject: [PATCH] refactor: Splitting ANSI into codes and texts, instead of converting straight to HTML --- CHANGELOG.md | 2 +- ICD.Common.Utils.Tests/AnsiUtilsTest.cs | 27 +++++ .../Converters/AnsiToHtmlTest.cs | 22 ---- ICD.Common.Utils/AnsiUtils.cs | 109 +++++++++++++++++- ICD.Common.Utils/Converters/AnsiToHtml.cs | 73 ------------ .../ICD.Common.Utils_SimplSharp.csproj | 1 - ICD.Common.Utils/RegexUtils.cs | 1 + 7 files changed, 137 insertions(+), 98 deletions(-) create mode 100644 ICD.Common.Utils.Tests/AnsiUtilsTest.cs delete mode 100644 ICD.Common.Utils.Tests/Converters/AnsiToHtmlTest.cs delete mode 100644 ICD.Common.Utils/Converters/AnsiToHtml.cs diff --git a/CHANGELOG.md b/CHANGELOG.md index 05f4de8..0920713 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0. - Added public access to GetValues enumeration extension - Added extensions for getting JsonReader values as long or ulong - Added DateTimeUtils methods for creating DateTimes from epoch seconds or milliseconds - - Added AnsiToHtml converter for visualizing ANSI logs + - Added utils for splitting ANSI into spans for conversion to XAML, HTML, etc ### Changed - Fixed exception trying to get DHCP status of network interfaces on Linux diff --git a/ICD.Common.Utils.Tests/AnsiUtilsTest.cs b/ICD.Common.Utils.Tests/AnsiUtilsTest.cs new file mode 100644 index 0000000..123961b --- /dev/null +++ b/ICD.Common.Utils.Tests/AnsiUtilsTest.cs @@ -0,0 +1,27 @@ +using System.Linq; +using NUnit.Framework; + +namespace ICD.Common.Utils.Tests +{ + [TestFixture] + public sealed class AnsiUtilsTest + { + [Test] + public void ToSpansTest() + { + string ansi = "\x1b[30mblack\x1b[37mwhite\x1b[0mdefault"; + AnsiSpan[] spans = AnsiUtils.ToSpans(ansi).ToArray(); + + Assert.AreEqual(3, spans.Length); + + Assert.AreEqual("black", spans[0].Text); + Assert.AreEqual("30", spans[0].Code); + + Assert.AreEqual("white", spans[1].Text); + Assert.AreEqual("37", spans[1].Code); + + Assert.AreEqual("default", spans[2].Text); + Assert.AreEqual("0", spans[2].Code); + } + } +} diff --git a/ICD.Common.Utils.Tests/Converters/AnsiToHtmlTest.cs b/ICD.Common.Utils.Tests/Converters/AnsiToHtmlTest.cs deleted file mode 100644 index 4d14e5a..0000000 --- a/ICD.Common.Utils.Tests/Converters/AnsiToHtmlTest.cs +++ /dev/null @@ -1,22 +0,0 @@ -using ICD.Common.Utils.Converters; -using NUnit.Framework; - -namespace ICD.Common.Utils.Tests.Converters -{ - [TestFixture] - public sealed class AnsiToHtmlTest - { - [TestCase("black\x1b[37mwhite", - @"blackwhite")] - [TestCase("black\x1b[0mblack", - @"blackblack")] - [TestCase("black\x1b[37mwhite\x1b[0m", - @"blackwhite")] - [TestCase("\x1b[30mblack\x1b[37mwhite", - @"blackwhite")] - public void ConvertTest(string ansi, string expected) - { - Assert.AreEqual(expected, AnsiToHtml.Convert(ansi)); - } - } -} diff --git a/ICD.Common.Utils/AnsiUtils.cs b/ICD.Common.Utils/AnsiUtils.cs index 71f4878..c1724ce 100644 --- a/ICD.Common.Utils/AnsiUtils.cs +++ b/ICD.Common.Utils/AnsiUtils.cs @@ -1,4 +1,7 @@ -namespace ICD.Common.Utils +using System.Collections.Generic; +using System.Text.RegularExpressions; + +namespace ICD.Common.Utils { public static class AnsiUtils { @@ -13,6 +16,42 @@ public const string CODE_RESET = "\x1b[0m"; + /// + /// Matches ANSI escape codes, e.g. \x1b[31m and \x1b[30;1m + /// + private const string ANSI_PATTERN = "(?'match'\x01b\\[(?'code'[\\d;]+)m)"; + + /// + /// Matches ANSI escape codes to HTML styles. + /// Color values are taken from PuTTY. + /// + private static readonly Dictionary s_PuttyColors = + new Dictionary + { + {"30", "000000"}, // Black + {"31", "BB0000"}, // Red + {"32", "00BB00"}, // Green + {"33", "BBBB00"}, // Yellow + {"34", "0000BB"}, // Blue + {"35", "BB00BB"}, // Magenta + {"36", "00BBBB"}, // Cyan + {"37", "BBBBBB"}, // White + + {"30;1", "555555"}, // Bright Black + {"31;1", "FF5555"}, // Bright Red + {"32;1", "55FF55"}, // Bright Green + {"33;1", "FFFF55"}, // Bright Yellow + {"34;1", "5555FF"}, // Bright Blue + {"35;1", "FF55FF"}, // Bright Magenta + {"36;1", "55FFFF"}, // Bright Cyan + {"37;1", "FFFFFF"}, // Bright White + }; + + /// + /// Gets the color map matching PuTTY. + /// + public static IDictionary PuttyColors { get { return s_PuttyColors; } } + /// /// Constructor. /// @@ -37,5 +76,73 @@ return string.Format("{0}{1}{2}", code, data, CODE_RESET); } + + /// + /// Splits the given ANSI string into spans. + /// + /// + /// + public static IEnumerable ToSpans(string data) + { + if (string.IsNullOrEmpty(data)) + yield break; + + Regex regex = new Regex(ANSI_PATTERN); + Match match = regex.Match(data); + + // No matches + if (!match.Success) + yield return new AnsiSpan {Text = data}; + + // Find the spans + while (match.Success) + { + // Get the code + string code = match.Groups["code"].Value; + + // Get the text + Match next = match.NextMatch(); + int startIndex = match.Index + match.Length; + int endIndex = next.Success ? next.Index : data.Length; + string text = data.Substring(startIndex, endIndex - startIndex); + + // Build the span + if (text.Length > 0) + yield return new AnsiSpan { Code = code, Text = text }; + + // Loop + match = next; + } + } + + /// + /// Removes the bright suffix from the code if present, otherwise appends a bright suffix. + /// + /// + /// + public static string InvertBright(string code) + { + return code.EndsWith(";1") + ? code.Substring(0, code.Length - 1) + : code + ";1"; + } + } + + public sealed class AnsiSpan + { + public string Code { get; set; } + public string Text { get; set; } + + /// + /// Gets the color value for the code. + /// + /// + /// + /// + public string GetColor(IDictionary colors, bool invertBright) + { + string code = invertBright ? AnsiUtils.InvertBright(Code) : Code; + return colors[code]; + } } } \ No newline at end of file diff --git a/ICD.Common.Utils/Converters/AnsiToHtml.cs b/ICD.Common.Utils/Converters/AnsiToHtml.cs deleted file mode 100644 index a003cf3..0000000 --- a/ICD.Common.Utils/Converters/AnsiToHtml.cs +++ /dev/null @@ -1,73 +0,0 @@ -using System.Collections.Generic; - -namespace ICD.Common.Utils.Converters -{ - public static class AnsiToHtml - { - /// - /// Matches ANSI escape codes, e.g. \x1b[31m and \x1b[30;1m - /// - private const string ANSI_PATTERN = "(?'match'\x01b\\[(?'code'[\\d;]+)m)"; - - private const string CODE_RESET = "0"; - - /// - /// Matches ANSI escape codes to HTML styles. - /// Color values are taken from PuTTY. - /// - private static readonly Dictionary s_Colors = - new Dictionary - { - {"30", "color:#000000"}, // Black - {"31", "color:#BB0000"}, // Red - {"32", "color:#00BB00"}, // Green - {"33", "color:#BBBB00"}, // Yellow - {"34", "color:#0000BB"}, // Blue - {"35", "color:#BB00BB"}, // Magenta - {"36", "color:#00BBBB"}, // Cyan - {"37", "color:#BBBBBB"}, // White - - {"30;1", "color:#555555"}, // Bright Black - {"31;1", "color:#FF5555"}, // Bright Red - {"32;1", "color:#55FF55"}, // Bright Green - {"33;1", "color:#FFFF55"}, // Bright Yellow - {"34;1", "color:#5555FF"}, // Bright Blue - {"35;1", "color:#FF55FF"}, // Bright Magenta - {"36;1", "color:#55FFFF"}, // Bright Cyan - {"37;1", "color:#FFFFFF"}, // Bright White - }; - - /// - /// Converts the input ansi string into html with color attributes. - /// - /// - /// - public static string Convert(string ansi) - { - int depth = 0; - - // Hack - Append a reset to close any open spans - ansi += AnsiUtils.CODE_RESET; - - return RegexUtils.ReplaceGroup(ansi, ANSI_PATTERN, "match", match => - { - string code = match.Groups["code"].Value; - - // Reset code - close all of the open spans. - if (code == CODE_RESET) - { - string output = StringUtils.Repeat("", depth); - depth = 0; - return output; - } - - string style; - if (!s_Colors.TryGetValue(code, out style)) - return match.Value; - - depth++; - return string.Format("", style); - }); - } - } -} \ No newline at end of file diff --git a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj index a917c72..7000084 100644 --- a/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj +++ b/ICD.Common.Utils/ICD.Common.Utils_SimplSharp.csproj @@ -87,7 +87,6 @@ - diff --git a/ICD.Common.Utils/RegexUtils.cs b/ICD.Common.Utils/RegexUtils.cs index 0db86a2..1d20994 100644 --- a/ICD.Common.Utils/RegexUtils.cs +++ b/ICD.Common.Utils/RegexUtils.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Linq; using System.Text; using System.Text.RegularExpressions;