Rename Style and Appearance

* Renames Style -> Decoration
* Renames Appearance -> Style
* Adds Style.Parse and Style.TryParse
This commit is contained in:
Patrik Svensson
2020-08-03 22:33:08 +02:00
committed by Patrik Svensson
parent c3286a4842
commit 98cf63f485
32 changed files with 691 additions and 405 deletions

View File

@ -63,12 +63,12 @@ namespace Spectre.Console
}
/// <summary>
/// Gets or sets the style.
/// Gets or sets the text decoration.
/// </summary>
public static Styles Style
public static Decoration Decoration
{
get => Console.Style;
set => Console.Style = value;
get => Console.Decoration;
set => Console.Decoration = value;
}
/// <summary>
@ -83,7 +83,7 @@ namespace Spectre.Console
}
/// <summary>
/// Resets colors and styles to the default ones.
/// Resets colors and text decorations.
/// </summary>
public static void Reset()
{
@ -91,15 +91,15 @@ namespace Spectre.Console
}
/// <summary>
/// Resets the current style back to the default one.
/// Resets the current applied text decorations.
/// </summary>
public static void ResetStyle()
public static void ResetDecoration()
{
Console.ResetStyle();
Console.ResetDecoration();
}
/// <summary>
/// Resets the foreground and background colors to the default ones.
/// Resets the current applied foreground and background colors.
/// </summary>
public static void ResetColors()
{

View File

@ -1,108 +0,0 @@
using System;
namespace Spectre.Console
{
/// <summary>
/// Represents color and style.
/// </summary>
public sealed class Appearance : IEquatable<Appearance>
{
/// <summary>
/// Gets the foreground color.
/// </summary>
public Color Foreground { get; }
/// <summary>
/// Gets the background color.
/// </summary>
public Color Background { get; }
/// <summary>
/// Gets the style.
/// </summary>
public Styles Style { get; }
/// <summary>
/// Gets an <see cref="Appearance"/> with the
/// default color and without style.
/// </summary>
public static Appearance Plain { get; } = new Appearance();
private Appearance()
: this(null, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Appearance"/> class.
/// </summary>
/// <param name="foreground">The foreground color.</param>
/// <param name="background">The background color.</param>
/// <param name="style">The style.</param>
public Appearance(Color? foreground = null, Color? background = null, Styles? style = null)
{
Foreground = foreground ?? Color.Default;
Background = background ?? Color.Default;
Style = style ?? Styles.None;
}
/// <summary>
/// Combines this appearance with another one.
/// </summary>
/// <param name="other">The item to combine with this.</param>
/// <returns>A new appearance representing a combination of this and the other one.</returns>
public Appearance Combine(Appearance other)
{
if (other is null)
{
throw new ArgumentNullException(nameof(other));
}
var foreground = Foreground;
if (!other.Foreground.IsDefault)
{
foreground = other.Foreground;
}
var background = Background;
if (!other.Background.IsDefault)
{
background = other.Background;
}
return new Appearance(foreground, background, Style | other.Style);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
var hash = (int)2166136261;
hash = (hash * 16777619) ^ Foreground.GetHashCode();
hash = (hash * 16777619) ^ Background.GetHashCode();
hash = (hash * 16777619) ^ Style.GetHashCode();
return hash;
}
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return Equals(obj as Appearance);
}
/// <inheritdoc/>
public bool Equals(Appearance other)
{
if (other == null)
{
return false;
}
return Foreground.Equals(other.Foreground) &&
Background.Equals(other.Background) &&
Style == other.Style;
}
}
}

View File

@ -25,16 +25,16 @@ namespace Spectre.Console.Composition
public bool IsLineBreak { get; }
/// <summary>
/// Gets the appearance of the segment.
/// Gets the segment style.
/// </summary>
public Appearance Appearance { get; }
public Style Style { get; }
/// <summary>
/// Initializes a new instance of the <see cref="Segment"/> class.
/// </summary>
/// <param name="text">The segment text.</param>
public Segment(string text)
: this(text, Appearance.Plain)
: this(text, Style.Plain)
{
}
@ -42,16 +42,16 @@ namespace Spectre.Console.Composition
/// Initializes a new instance of the <see cref="Segment"/> class.
/// </summary>
/// <param name="text">The segment text.</param>
/// <param name="appearance">The segment appearance.</param>
public Segment(string text, Appearance appearance)
: this(text, appearance, false)
/// <param name="style">The segment style.</param>
public Segment(string text, Style style)
: this(text, style, false)
{
}
private Segment(string text, Appearance appearance, bool lineBreak)
private Segment(string text, Style style, bool lineBreak)
{
Text = text?.NormalizeLineEndings() ?? throw new ArgumentNullException(nameof(text));
Appearance = appearance;
Style = style;
IsLineBreak = lineBreak;
}
@ -61,7 +61,7 @@ namespace Spectre.Console.Composition
/// <returns>A segment that represents an implicit line break.</returns>
public static Segment LineBreak()
{
return new Segment("\n", Appearance.Plain, true);
return new Segment("\n", Style.Plain, true);
}
/// <summary>
@ -81,7 +81,7 @@ namespace Spectre.Console.Composition
/// <returns>A new segment without any trailing line endings.</returns>
public Segment StripLineEndings()
{
return new Segment(Text.TrimEnd('\n'), Appearance);
return new Segment(Text.TrimEnd('\n'), Style);
}
/// <summary>
@ -102,8 +102,8 @@ namespace Spectre.Console.Composition
}
return (
new Segment(Text.Substring(0, offset), Appearance),
new Segment(Text.Substring(offset, Text.Length - offset), Appearance));
new Segment(Text.Substring(0, offset), Style),
new Segment(Text.Substring(offset, Text.Length - offset), Style));
}
/// <summary>
@ -178,7 +178,7 @@ namespace Spectre.Console.Composition
{
if (parts[0].Length > 0)
{
line.Add(new Segment(parts[0], segment.Appearance));
line.Add(new Segment(parts[0], segment.Style));
}
}

View File

@ -9,7 +9,7 @@ using Spectre.Console.Internal;
namespace Spectre.Console
{
/// <summary>
/// Represents text with color and style.
/// Represents text with color and decorations.
/// </summary>
[SuppressMessage("Naming", "CA1724:Type names should not match namespaces")]
public sealed class Text : IRenderable
@ -21,13 +21,13 @@ namespace Spectre.Console
{
public int Start { get; }
public int End { get; }
public Appearance Appearance { get; }
public Style Style { get; }
public Span(int start, int end, Appearance appearance)
public Span(int start, int end, Style style)
{
Start = start;
End = end;
Appearance = appearance ?? Appearance.Plain;
Style = style ?? Style.Plain;
}
}
@ -47,21 +47,21 @@ namespace Spectre.Console
/// <param name="text">The text.</param>
/// <param name="foreground">The foreground.</param>
/// <param name="background">The background.</param>
/// <param name="style">The style.</param>
/// <param name="decoration">The text decoration.</param>
/// <returns>A <see cref="Text"/> instance.</returns>
public static Text New(
string text, Color? foreground = null, Color? background = null, Styles? style = null)
string text, Color? foreground = null, Color? background = null, Decoration? decoration = null)
{
var result = MarkupParser.Parse(text, new Appearance(foreground, background, style));
var result = MarkupParser.Parse(text, new Style(foreground, background, decoration));
return result;
}
/// <summary>
/// Appends some text with a style.
/// Appends some text with the specified color and decorations.
/// </summary>
/// <param name="text">The text to append.</param>
/// <param name="appearance">The appearance of the text.</param>
public void Append(string text, Appearance appearance)
/// <param name="style">The text style.</param>
public void Append(string text, Style style)
{
if (text == null)
{
@ -73,7 +73,7 @@ namespace Spectre.Console
_text += text;
Stylize(start, end, appearance);
Stylize(start, end, style);
}
/// <summary>
@ -81,8 +81,8 @@ namespace Spectre.Console
/// </summary>
/// <param name="start">The start position.</param>
/// <param name="end">The end position.</param>
/// <param name="appearance">The color and style to apply.</param>
public void Stylize(int start, int end, Appearance appearance)
/// <param name="style">The style to apply.</param>
public void Stylize(int start, int end, Style style)
{
if (start >= end)
{
@ -92,7 +92,7 @@ namespace Spectre.Console
start = Math.Max(start, 0);
end = Math.Min(end, _text.Length);
_spans.Add(new Span(start, end, appearance));
_spans.Add(new Span(start, end, style));
}
/// <inheritdoc/>
@ -149,7 +149,7 @@ namespace Spectre.Console
}
result.Add(Segment.LineBreak());
queue.Enqueue(new Segment(second.Text.Substring(1), second.Appearance));
queue.Enqueue(new Segment(second.Text.Substring(1), second.Style));
}
}
@ -162,8 +162,8 @@ namespace Spectre.Console
// https://github.com/willmcgugan/rich/blob/eb2f0d5277c159d8693636ec60c79c5442fd2e43/rich/text.py#L492
// Create the style map.
var styleMap = _spans.SelectIndex((span, index) => (span, index)).ToDictionary(x => x.index + 1, x => x.span.Appearance);
styleMap[0] = Appearance.Plain;
var styleMap = _spans.SelectIndex((span, index) => (span, index)).ToDictionary(x => x.index + 1, x => x.span.Style);
styleMap[0] = Style.Plain;
// Create a span list.
var spans = new List<(int Offset, bool Leaving, int Style)>();
@ -173,7 +173,7 @@ namespace Spectre.Console
spans.Add((_text.Length, true, 0));
spans = spans.OrderBy(x => x.Offset).ThenBy(x => !x.Leaving).ToList();
// Keep track of applied appearances using a stack
// Keep track of applied styles using a stack
var styleStack = new Stack<int>();
// Now build the segments.
@ -195,7 +195,7 @@ namespace Spectre.Console
{
// Build the current style from the stack
var styleIndices = styleStack.OrderBy(index => index).ToArray();
var currentStyle = Appearance.Plain.Combine(styleIndices.Select(index => styleMap[index]));
var currentStyle = Style.Plain.Combine(styleIndices.Select(index => styleMap[index]));
// Create segment
var text = _text.Substring(offset, Math.Min(_text.Length - offset, nextOffset - offset));

View File

@ -28,9 +28,9 @@ namespace Spectre.Console
foreach (var segment in renderable.Render(console.Encoding, console.Width))
{
if (!segment.Appearance.Equals(Appearance.Plain))
if (!segment.Style.Equals(Style.Plain))
{
using (var appearance = console.PushAppearance(segment.Appearance))
using (var style = console.PushStyle(segment.Style))
{
console.Write(segment.Text);
}

View File

@ -8,7 +8,7 @@ namespace Spectre.Console
public static partial class ConsoleExtensions
{
/// <summary>
/// Resets both colors and style for the console.
/// Resets colors and text decorations.
/// </summary>
/// <param name="console">The console to reset.</param>
public static void Reset(this IAnsiConsole console)
@ -19,25 +19,25 @@ namespace Spectre.Console
}
console.ResetColors();
console.ResetStyle();
console.ResetDecoration();
}
/// <summary>
/// Resets the current style back to the default one.
/// Resets the current applied text decorations.
/// </summary>
/// <param name="console">The console to reset the style for.</param>
public static void ResetStyle(this IAnsiConsole console)
/// <param name="console">The console to reset the text decorations for.</param>
public static void ResetDecoration(this IAnsiConsole console)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
console.Style = Styles.None;
console.Decoration = Decoration.None;
}
/// <summary>
/// Resets the foreground and background colors to the default ones.
/// Resets the current applied foreground and background colors.
/// </summary>
/// <param name="console">The console to reset colors for.</param>
public static void ResetColors(this IAnsiConsole console)

View File

@ -1,18 +1,20 @@
using System;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console
{
/// <summary>
/// Represents a style.
/// Represents text decoration.
/// </summary>
/// <remarks>
/// Support for different styles is up to the terminal.
/// Support for text decorations is up to the terminal.
/// </remarks>
[Flags]
public enum Styles
[SuppressMessage("Naming", "CA1714:Flags enums should have plural names")]
public enum Decoration
{
/// <summary>
/// No style.
/// No text decoration.
/// </summary>
None = 0,

View File

@ -28,9 +28,9 @@ namespace Spectre.Console
Encoding Encoding { get; }
/// <summary>
/// Gets or sets the current style.
/// Gets or sets the current text decoration.
/// </summary>
Styles Style { get; set; }
Decoration Decoration { get; set; }
/// <summary>
/// Gets or sets the current foreground.

View File

@ -7,11 +7,11 @@ namespace Spectre.Console.Internal
public static string GetAnsi(
ColorSystem system,
string text,
Styles style,
Decoration decoration,
Color foreground,
Color background)
{
var codes = AnsiStyleBuilder.GetAnsiCodes(style);
var codes = AnsiDecorationBuilder.GetAnsiCodes(decoration);
// Got foreground?
if (foreground != Color.Default)

View File

@ -2,52 +2,52 @@ using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class AnsiStyleBuilder
internal static class AnsiDecorationBuilder
{
// TODO: Rewrite this to not yield
public static IEnumerable<byte> GetAnsiCodes(Styles style)
public static IEnumerable<byte> GetAnsiCodes(Decoration decoration)
{
if ((style & Styles.Bold) != 0)
if ((decoration & Decoration.Bold) != 0)
{
yield return 1;
}
if ((style & Styles.Dim) != 0)
if ((decoration & Decoration.Dim) != 0)
{
yield return 2;
}
if ((style & Styles.Italic) != 0)
if ((decoration & Decoration.Italic) != 0)
{
yield return 3;
}
if ((style & Styles.Underline) != 0)
if ((decoration & Decoration.Underline) != 0)
{
yield return 4;
}
if ((style & Styles.SlowBlink) != 0)
if ((decoration & Decoration.SlowBlink) != 0)
{
yield return 5;
}
if ((style & Styles.RapidBlink) != 0)
if ((decoration & Decoration.RapidBlink) != 0)
{
yield return 6;
}
if ((style & Styles.Invert) != 0)
if ((decoration & Decoration.Invert) != 0)
{
yield return 7;
}
if ((style & Styles.Conceal) != 0)
if ((decoration & Decoration.Conceal) != 0)
{
yield return 8;
}
if ((style & Styles.Strikethrough) != 0)
if ((decoration & Decoration.Strikethrough) != 0)
{
yield return 9;
}

View File

@ -11,7 +11,7 @@ namespace Spectre.Console.Internal
public Capabilities Capabilities { get; }
public Encoding Encoding { get; }
public Styles Style { get; set; }
public Decoration Decoration { get; set; }
public Color Foreground { get; set; }
public Color Background { get; set; }
@ -50,21 +50,7 @@ namespace Spectre.Console.Internal
Encoding = @out.IsStandardOut() ? System.Console.OutputEncoding : Encoding.UTF8;
Foreground = Color.Default;
Background = Color.Default;
Style = Styles.None;
}
public void Reset(bool colors, bool styles)
{
if (colors)
{
Foreground = Color.Default;
Background = Color.Default;
}
if (styles)
{
Style = Styles.None;
}
Decoration = Decoration.None;
}
public void Write(string text)
@ -77,7 +63,7 @@ namespace Spectre.Console.Internal
_out.Write(AnsiBuilder.GetAnsi(
_system,
text.NormalizeLineEndings(native: true),
Style,
Decoration,
Foreground,
Background));
}

View File

@ -25,7 +25,7 @@ namespace Spectre.Console.Internal
{
return new AnsiConsoleRenderer(buffer, colorSystem)
{
Style = Styles.None,
Decoration = Decoration.None,
};
}

View File

@ -0,0 +1,35 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
internal static class DecorationTable
{
private static readonly Dictionary<string, Decoration?> _lookup;
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
static DecorationTable()
{
_lookup = new Dictionary<string, Decoration?>(StringComparer.OrdinalIgnoreCase)
{
{ "none", Decoration.None },
{ "bold", Decoration.Bold },
{ "dim", Decoration.Dim },
{ "italic", Decoration.Italic },
{ "underline", Decoration.Underline },
{ "invert", Decoration.Invert },
{ "conceal", Decoration.Conceal },
{ "slowblink", Decoration.SlowBlink },
{ "rapidblink", Decoration.RapidBlink },
{ "strikethrough", Decoration.Strikethrough },
};
}
public static Decoration? GetDecoration(string name)
{
_lookup.TryGetValue(name, out var result);
return result;
}
}
}

View File

@ -1,18 +0,0 @@
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class AppearanceExtensions
{
public static Appearance Combine(this Appearance appearance, IEnumerable<Appearance> source)
{
var current = appearance;
foreach (var item in source)
{
current = current.Combine(item);
}
return current;
}
}
}

View File

@ -5,18 +5,23 @@ namespace Spectre.Console.Internal
{
internal static class ConsoleExtensions
{
public static IDisposable PushAppearance(this IAnsiConsole console, Appearance appearance)
public static IDisposable PushStyle(this IAnsiConsole console, Style style)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = new Appearance(console.Foreground, console.Background, console.Style);
console.SetColor(appearance.Foreground, true);
console.SetColor(appearance.Background, false);
console.Style = appearance.Style;
return new AppearanceScope(console, current);
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
var current = new Style(console.Foreground, console.Background, console.Decoration);
console.SetColor(style.Foreground, true);
console.SetColor(style.Background, false);
console.Decoration = style.Decoration;
return new StyleScope(console, current);
}
public static IDisposable PushColor(this IAnsiConsole console, Color color, bool foreground)
@ -31,16 +36,16 @@ namespace Spectre.Console.Internal
return new ColorScope(console, current, foreground);
}
public static IDisposable PushStyle(this IAnsiConsole console, Styles style)
public static IDisposable PushDecoration(this IAnsiConsole console, Decoration decoration)
{
if (console is null)
{
throw new ArgumentNullException(nameof(console));
}
var current = console.Style;
console.Style = style;
return new StyleScope(console, current);
var current = console.Decoration;
console.Decoration = decoration;
return new DecorationScope(console, current);
}
public static void SetColor(this IAnsiConsole console, Color color, bool foreground)
@ -61,30 +66,30 @@ namespace Spectre.Console.Internal
}
}
internal sealed class AppearanceScope : IDisposable
internal sealed class StyleScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Appearance _apperance;
private readonly Style _style;
public AppearanceScope(IAnsiConsole console, Appearance appearance)
public StyleScope(IAnsiConsole console, Style style)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_apperance = appearance;
_style = style ?? throw new ArgumentNullException(nameof(style));
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~AppearanceScope()
~StyleScope()
{
throw new InvalidOperationException("Appearance scope was not disposed.");
throw new InvalidOperationException("Style scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.SetColor(_apperance.Foreground, true);
_console.SetColor(_apperance.Background, false);
_console.Style = _apperance.Style;
_console.SetColor(_style.Foreground, true);
_console.SetColor(_style.Background, false);
_console.Decoration = _style.Decoration;
}
}
@ -115,28 +120,28 @@ namespace Spectre.Console.Internal
}
}
internal sealed class StyleScope : IDisposable
internal sealed class DecorationScope : IDisposable
{
private readonly IAnsiConsole _console;
private readonly Styles _style;
private readonly Decoration _decoration;
public StyleScope(IAnsiConsole console, Styles color)
public DecorationScope(IAnsiConsole console, Decoration decoration)
{
_console = console ?? throw new ArgumentNullException(nameof(console));
_style = color;
_decoration = decoration;
}
[SuppressMessage("Design", "CA1065:Do not raise exceptions in unexpected locations")]
[SuppressMessage("Performance", "CA1821:Remove empty Finalizers")]
~StyleScope()
~DecorationScope()
{
throw new InvalidOperationException("Style scope was not disposed.");
throw new InvalidOperationException("Decoration scope was not disposed.");
}
public void Dispose()
{
GC.SuppressFinalize(this);
_console.Style = _style;
_console.Decoration = _decoration;
}
}
}

View File

@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
namespace Spectre.Console.Internal
{
internal static class StyleExtensions
{
public static Style Combine(this Style style, IEnumerable<Style> source)
{
if (style is null)
{
throw new ArgumentNullException(nameof(style));
}
if (source is null)
{
return style;
}
var current = style;
foreach (var item in source)
{
current = current.Combine(item);
}
return current;
}
}
}

View File

@ -44,7 +44,7 @@ namespace Spectre.Console.Internal
}
}
public Styles Style { get; set; }
public Decoration Decoration { get; set; }
public Color Foreground
{

View File

@ -1,34 +0,0 @@
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
namespace Spectre.Console.Internal
{
internal static class StyleTable
{
private static readonly Dictionary<string, Styles?> _styles;
[SuppressMessage("Performance", "CA1810:Initialize reference type static fields inline")]
static StyleTable()
{
_styles = new Dictionary<string, Styles?>(StringComparer.OrdinalIgnoreCase)
{
{ "bold", Styles.Bold },
{ "dim", Styles.Dim },
{ "italic", Styles.Italic },
{ "underline", Styles.Underline },
{ "invert", Styles.Invert },
{ "conceal", Styles.Conceal },
{ "slowblink", Styles.SlowBlink },
{ "rapidblink", Styles.RapidBlink },
{ "strikethrough", Styles.Strikethrough },
};
}
public static Styles? GetStyle(string name)
{
_styles.TryGetValue(name, out var style);
return style;
}
}
}

View File

@ -5,14 +5,14 @@ namespace Spectre.Console.Internal
{
internal static class MarkupParser
{
public static Text Parse(string text, Appearance appearance = null)
public static Text Parse(string text, Style style = null)
{
appearance ??= Appearance.Plain;
style ??= Style.Plain;
var result = new Text(string.Empty);
using var tokenizer = new MarkupTokenizer(text);
var stack = new Stack<Appearance>();
var stack = new Stack<Style>();
while (tokenizer.MoveNext())
{
@ -20,8 +20,8 @@ namespace Spectre.Console.Internal
if (token.Kind == MarkupTokenKind.Open)
{
var (style, foreground, background) = MarkupStyleParser.Parse(token.Value);
stack.Push(new Appearance(foreground, background, style));
var parsedStyle = StyleParser.Parse(token.Value);
stack.Push(parsedStyle);
}
else if (token.Kind == MarkupTokenKind.Close)
{
@ -35,8 +35,8 @@ namespace Spectre.Console.Internal
else if (token.Kind == MarkupTokenKind.Text)
{
// Get the effecive style.
var style = appearance.Combine(stack);
result.Append(token.Value, style);
var effectiveStyle = style.Combine(stack);
result.Append(token.Value, effectiveStyle);
}
else
{

View File

@ -1,65 +0,0 @@
using System;
namespace Spectre.Console.Internal
{
internal static class MarkupStyleParser
{
public static (Styles? Style, Color? Foreground, Color? Background) Parse(string text)
{
var effectiveStyle = (Styles?)null;
var effectiveForeground = (Color?)null;
var effectiveBackground = (Color?)null;
var parts = text.Split(new[] { ' ' });
var foreground = true;
foreach (var part in parts)
{
if (part.Equals("on", StringComparison.OrdinalIgnoreCase))
{
foreground = false;
continue;
}
var style = StyleTable.GetStyle(part);
if (style != null)
{
if (effectiveStyle == null)
{
effectiveStyle = Styles.None;
}
effectiveStyle |= style.Value;
}
else
{
var color = ColorTable.GetColor(part);
if (color == null)
{
throw new InvalidOperationException("Could not find color..");
}
if (foreground)
{
if (effectiveForeground != null)
{
throw new InvalidOperationException("A foreground has already been set.");
}
effectiveForeground = color;
}
else
{
if (effectiveBackground != null)
{
throw new InvalidOperationException("A background has already been set.");
}
effectiveBackground = color;
}
}
}
return (effectiveStyle, effectiveForeground, effectiveBackground);
}
}
}

View File

@ -0,0 +1,104 @@
using System;
namespace Spectre.Console.Internal
{
internal static class StyleParser
{
public static Style Parse(string text)
{
var style = Parse(text, out var error);
if (error != null)
{
throw new InvalidOperationException(error);
}
return style;
}
public static bool TryParse(string text, out Style style)
{
style = Parse(text, out var error);
if (error != null)
{
return false;
}
return true;
}
private static Style Parse(string text, out string error)
{
var effectiveDecoration = (Decoration?)null;
var effectiveForeground = (Color?)null;
var effectiveBackground = (Color?)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;
}
var decoration = DecorationTable.GetDecoration(part);
if (decoration != null)
{
if (effectiveDecoration == null)
{
effectiveDecoration = Decoration.None;
}
effectiveDecoration |= decoration.Value;
}
else
{
var color = ColorTable.GetColor(part);
if (color == null)
{
if (!foreground)
{
error = $"Could not find color '{part}'.";
}
else
{
error = $"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);
}
}
}

View File

@ -0,0 +1,134 @@
using System;
using Spectre.Console.Internal;
namespace Spectre.Console
{
/// <summary>
/// Represents color and text decoration.
/// </summary>
public sealed class Style : IEquatable<Style>
{
/// <summary>
/// Gets the foreground color.
/// </summary>
public Color Foreground { get; }
/// <summary>
/// Gets the background color.
/// </summary>
public Color Background { get; }
/// <summary>
/// Gets the text decoration.
/// </summary>
public Decoration Decoration { get; }
/// <summary>
/// Gets an <see cref="Style"/> with the
/// default colors and without text decoration.
/// </summary>
public static Style Plain { get; } = new Style();
private Style()
: this(null, null, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Style"/> class.
/// </summary>
/// <param name="foreground">The foreground color.</param>
/// <param name="background">The background color.</param>
/// <param name="decoration">The text decoration.</param>
public Style(Color? foreground = null, Color? background = null, Decoration? decoration = null)
{
Foreground = foreground ?? Color.Default;
Background = background ?? Color.Default;
Decoration = decoration ?? Decoration.None;
}
/// <summary>
/// Converts the string representation of a style to its <see cref="Style"/> equivalent.
/// </summary>
/// <param name="text">A string containing a style to parse.</param>
/// <returns>A <see cref="Style"/> equivalent of the text contained in <paramref name="text"/>.</returns>
public static Style Parse(string text)
{
return StyleParser.Parse(text);
}
/// <summary>
/// Converts the string representation of a style to its <see cref="Style"/> equivalent.
/// A return value indicates whether the operation succeeded.
/// </summary>
/// <param name="text">A string containing a style to parse.</param>
/// <param name="result">
/// When this method returns, contains the <see cref="Style"/> equivalent of the text contained in <paramref name="text"/>,
/// if the conversion succeeded, or <c>null</c> if the conversion failed.
/// </param>
/// <returns><c>true</c> if s was converted successfully; otherwise, <c>false</c>.</returns>
public static bool TryParse(string text, out Style result)
{
return StyleParser.TryParse(text, out result);
}
/// <summary>
/// Combines this style with another one.
/// </summary>
/// <param name="other">The item to combine with this.</param>
/// <returns>A new style representing a combination of this and the other one.</returns>
public Style Combine(Style other)
{
if (other is null)
{
throw new ArgumentNullException(nameof(other));
}
var foreground = Foreground;
if (!other.Foreground.IsDefault)
{
foreground = other.Foreground;
}
var background = Background;
if (!other.Background.IsDefault)
{
background = other.Background;
}
return new Style(foreground, background, Decoration | other.Decoration);
}
/// <inheritdoc/>
public override int GetHashCode()
{
unchecked
{
var hash = (int)2166136261;
hash = (hash * 16777619) ^ Foreground.GetHashCode();
hash = (hash * 16777619) ^ Background.GetHashCode();
hash = (hash * 16777619) ^ Decoration.GetHashCode();
return hash;
}
}
/// <inheritdoc/>
public override bool Equals(object obj)
{
return Equals(obj as Style);
}
/// <inheritdoc/>
public bool Equals(Style other)
{
if (other == null)
{
return false;
}
return Foreground.Equals(other.Foreground) &&
Background.Equals(other.Background) &&
Decoration == other.Decoration;
}
}
}