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 public access to GetValues enumeration extension
|
||||||
- Added extensions for getting JsonReader values as long or ulong
|
- Added extensions for getting JsonReader values as long or ulong
|
||||||
- Added DateTimeUtils methods for creating DateTimes from epoch seconds or milliseconds
|
- 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
|
### Changed
|
||||||
- Fixed exception trying to get DHCP status of network interfaces on Linux
|
- 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
|
public static class AnsiUtils
|
||||||
{
|
{
|
||||||
@@ -13,6 +16,42 @@
|
|||||||
|
|
||||||
public const string CODE_RESET = "\x1b[0m";
|
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>
|
/// <summary>
|
||||||
/// Constructor.
|
/// Constructor.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -37,5 +76,73 @@
|
|||||||
|
|
||||||
return string.Format("{0}{1}{2}", code, data, CODE_RESET);
|
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\PredicateComparer.cs" />
|
||||||
<Compile Include="Comparers\SequenceComparer.cs" />
|
<Compile Include="Comparers\SequenceComparer.cs" />
|
||||||
<Compile Include="eConsoleColor.cs" />
|
<Compile Include="eConsoleColor.cs" />
|
||||||
<Compile Include="Converters\AnsiToHtml.cs" />
|
|
||||||
<Compile Include="DateTimeUtils.cs" />
|
<Compile Include="DateTimeUtils.cs" />
|
||||||
<Compile Include="Email\EmailClient.cs" />
|
<Compile Include="Email\EmailClient.cs" />
|
||||||
<Compile Include="Email\eMailErrorCode.cs" />
|
<Compile Include="Email\eMailErrorCode.cs" />
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System;
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
|
|||||||
Reference in New Issue
Block a user