mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-12-28 19:45:48 +08:00
committed by
Patrik Svensson
parent
913a7b1e37
commit
a23bec4082
215
src/Spectre.Console/StyleParser.cs
Normal file
215
src/Spectre.Console/StyleParser.cs
Normal file
@@ -0,0 +1,215 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
|
||||
namespace Spectre.Console
|
||||
{
|
||||
internal static class StyleParser
|
||||
{
|
||||
public static Style Parse(string text)
|
||||
{
|
||||
var style = Parse(text, out var error);
|
||||
if (error != null)
|
||||
{
|
||||
throw new InvalidOperationException(error);
|
||||
}
|
||||
|
||||
if (style == null)
|
||||
{
|
||||
// This should not happen, but we need to please the compiler
|
||||
// which cannot know that style isn't null here.
|
||||
throw new InvalidOperationException("Could not parse style.");
|
||||
}
|
||||
|
||||
return style;
|
||||
}
|
||||
|
||||
public static bool TryParse(string text, out Style? style)
|
||||
{
|
||||
style = Parse(text, out var error);
|
||||
return error == null;
|
||||
}
|
||||
|
||||
private static Style? Parse(string text, out string? error)
|
||||
{
|
||||
var effectiveDecoration = (Decoration?)null;
|
||||
var effectiveForeground = (Color?)null;
|
||||
var effectiveBackground = (Color?)null;
|
||||
var effectiveLink = (string?)null;
|
||||
|
||||
var parts = text.Split(new[] { ' ' });
|
||||
var foreground = true;
|
||||
foreach (var part in parts)
|
||||
{
|
||||
if (part.Equals("default", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
foreground = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.StartsWith("link=", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
if (effectiveLink != null)
|
||||
{
|
||||
error = "A link has already been set.";
|
||||
return null;
|
||||
}
|
||||
|
||||
effectiveLink = part.Substring(5);
|
||||
continue;
|
||||
}
|
||||
else if (part.StartsWith("link", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
effectiveLink = Constants.EmptyLink;
|
||||
continue;
|
||||
}
|
||||
|
||||
var decoration = DecorationTable.GetDecoration(part);
|
||||
if (decoration != null)
|
||||
{
|
||||
effectiveDecoration ??= Decoration.None;
|
||||
|
||||
effectiveDecoration |= decoration.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var color = ColorTable.GetColor(part);
|
||||
if (color == null)
|
||||
{
|
||||
if (part.StartsWith("#", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
color = ParseHexColor(part, out error);
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else if (part.StartsWith("rgb", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
color = ParseRgbColor(part, out error);
|
||||
if (!string.IsNullOrWhiteSpace(error))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
error = !foreground
|
||||
? $"Could not find color '{part}'."
|
||||
: $"Could not find color or style '{part}'.";
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
if (foreground)
|
||||
{
|
||||
if (effectiveForeground != null)
|
||||
{
|
||||
error = "A foreground color has already been set.";
|
||||
return null;
|
||||
}
|
||||
|
||||
effectiveForeground = color;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (effectiveBackground != null)
|
||||
{
|
||||
error = "A background color has already been set.";
|
||||
return null;
|
||||
}
|
||||
|
||||
effectiveBackground = color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
error = null;
|
||||
return new Style(
|
||||
effectiveForeground,
|
||||
effectiveBackground,
|
||||
effectiveDecoration,
|
||||
effectiveLink);
|
||||
}
|
||||
|
||||
private static Color? ParseHexColor(string hex, out string? error)
|
||||
{
|
||||
error = null;
|
||||
|
||||
hex ??= string.Empty;
|
||||
hex = hex.ReplaceExact("#", string.Empty).Trim();
|
||||
|
||||
try
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(hex))
|
||||
{
|
||||
if (hex.Length == 6)
|
||||
{
|
||||
return new Color(
|
||||
(byte)Convert.ToUInt32(hex.Substring(0, 2), 16),
|
||||
(byte)Convert.ToUInt32(hex.Substring(2, 2), 16),
|
||||
(byte)Convert.ToUInt32(hex.Substring(4, 2), 16));
|
||||
}
|
||||
else if (hex.Length == 3)
|
||||
{
|
||||
return new Color(
|
||||
(byte)Convert.ToUInt32(new string(hex[0], 2), 16),
|
||||
(byte)Convert.ToUInt32(new string(hex[1], 2), 16),
|
||||
(byte)Convert.ToUInt32(new string(hex[2], 2), 16));
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Invalid hex color '#{hex}'. {ex.Message}";
|
||||
return null;
|
||||
}
|
||||
|
||||
error = $"Invalid hex color '#{hex}'.";
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Color? ParseRgbColor(string rgb, out string? error)
|
||||
{
|
||||
try
|
||||
{
|
||||
error = null;
|
||||
|
||||
var normalized = rgb ?? string.Empty;
|
||||
if (normalized.Length >= 3)
|
||||
{
|
||||
// Trim parentheses
|
||||
normalized = normalized.Substring(3).Trim();
|
||||
|
||||
if (normalized.StartsWith("(", StringComparison.OrdinalIgnoreCase) &&
|
||||
normalized.EndsWith(")", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
normalized = normalized.Trim('(').Trim(')');
|
||||
|
||||
var parts = normalized.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (parts.Length == 3)
|
||||
{
|
||||
return new Color(
|
||||
(byte)Convert.ToInt32(parts[0], CultureInfo.InvariantCulture),
|
||||
(byte)Convert.ToInt32(parts[1], CultureInfo.InvariantCulture),
|
||||
(byte)Convert.ToInt32(parts[2], CultureInfo.InvariantCulture));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = $"Invalid RGB color '{rgb}'. {ex.Message}";
|
||||
return null;
|
||||
}
|
||||
|
||||
error = $"Invalid RGB color '{rgb}'.";
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user