mirror of
https://github.com/ICDSystems/ICD.Common.Utils.git
synced 2026-01-11 19:44:55 +00:00
refactor: Splitting ANSI into codes and texts, instead of converting straight to HTML
This commit is contained in:
@@ -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
|
||||
|
||||
27
ICD.Common.Utils.Tests/AnsiUtilsTest.cs
Normal file
27
ICD.Common.Utils.Tests/AnsiUtilsTest.cs
Normal file
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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",
|
||||
@"black<span style=""color:#BBBBBB"">white</span>")]
|
||||
[TestCase("black\x1b[0mblack",
|
||||
@"blackblack")]
|
||||
[TestCase("black\x1b[37mwhite\x1b[0m",
|
||||
@"black<span style=""color:#BBBBBB"">white</span>")]
|
||||
[TestCase("\x1b[30mblack\x1b[37mwhite",
|
||||
@"<span style=""color:#000000"">black<span style=""color:#BBBBBB"">white</span></span>")]
|
||||
public void ConvertTest(string ansi, string expected)
|
||||
{
|
||||
Assert.AreEqual(expected, AnsiToHtml.Convert(ansi));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
/// <summary>
|
||||
/// Matches ANSI escape codes, e.g. \x1b[31m and \x1b[30;1m
|
||||
/// </summary>
|
||||
private const string ANSI_PATTERN = "(?'match'\x01b\\[(?'code'[\\d;]+)m)";
|
||||
|
||||
/// <summary>
|
||||
/// Matches ANSI escape codes to HTML styles.
|
||||
/// Color values are taken from PuTTY.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, string> s_PuttyColors =
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{"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
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color map matching PuTTY.
|
||||
/// </summary>
|
||||
public static IDictionary<string, string> PuttyColors { get { return s_PuttyColors; } }
|
||||
|
||||
/// <summary>
|
||||
/// Constructor.
|
||||
/// </summary>
|
||||
@@ -37,5 +76,73 @@
|
||||
|
||||
return string.Format("{0}{1}{2}", code, data, CODE_RESET);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Splits the given ANSI string into spans.
|
||||
/// </summary>
|
||||
/// <param name="data"></param>
|
||||
/// <returns></returns>
|
||||
public static IEnumerable<AnsiSpan> 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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Removes the bright suffix from the code if present, otherwise appends a bright suffix.
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
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; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the color value for the code.
|
||||
/// </summary>
|
||||
/// <param name="colors"></param>
|
||||
/// <param name="invertBright"></param>
|
||||
/// <returns></returns>
|
||||
public string GetColor(IDictionary<string, string> colors, bool invertBright)
|
||||
{
|
||||
string code = invertBright ? AnsiUtils.InvertBright(Code) : Code;
|
||||
return colors[code];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,73 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace ICD.Common.Utils.Converters
|
||||
{
|
||||
public static class AnsiToHtml
|
||||
{
|
||||
/// <summary>
|
||||
/// Matches ANSI escape codes, e.g. \x1b[31m and \x1b[30;1m
|
||||
/// </summary>
|
||||
private const string ANSI_PATTERN = "(?'match'\x01b\\[(?'code'[\\d;]+)m)";
|
||||
|
||||
private const string CODE_RESET = "0";
|
||||
|
||||
/// <summary>
|
||||
/// Matches ANSI escape codes to HTML styles.
|
||||
/// Color values are taken from PuTTY.
|
||||
/// </summary>
|
||||
private static readonly Dictionary<string, string> s_Colors =
|
||||
new Dictionary<string, string>
|
||||
{
|
||||
{"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
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Converts the input ansi string into html with color attributes.
|
||||
/// </summary>
|
||||
/// <param name="ansi"></param>
|
||||
/// <returns></returns>
|
||||
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("</span>", depth);
|
||||
depth = 0;
|
||||
return output;
|
||||
}
|
||||
|
||||
string style;
|
||||
if (!s_Colors.TryGetValue(code, out style))
|
||||
return match.Value;
|
||||
|
||||
depth++;
|
||||
return string.Format("<span style=\"{0}\">", style);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,7 +87,6 @@
|
||||
<Compile Include="Comparers\PredicateComparer.cs" />
|
||||
<Compile Include="Comparers\SequenceComparer.cs" />
|
||||
<Compile Include="eConsoleColor.cs" />
|
||||
<Compile Include="Converters\AnsiToHtml.cs" />
|
||||
<Compile Include="DateTimeUtils.cs" />
|
||||
<Compile Include="Email\EmailClient.cs" />
|
||||
<Compile Include="Email\eMailErrorCode.cs" />
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
Reference in New Issue
Block a user