mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-19 21:38:16 +08:00
Add parser and renderer for markup language
This commit is contained in:

committed by
Patrik Svensson

parent
b72a695c35
commit
0986a5f744
@ -1,73 +0,0 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class Composer : IRenderable
|
||||
{
|
||||
private readonly BlockElement _root;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length => _root.Length;
|
||||
|
||||
public Composer()
|
||||
{
|
||||
_root = new BlockElement();
|
||||
}
|
||||
|
||||
public static Composer New()
|
||||
{
|
||||
return new Composer();
|
||||
}
|
||||
|
||||
public Composer Text(string text)
|
||||
{
|
||||
_root.Append(new TextElement(text));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Composer Foreground(Color color, Action<Composer> action)
|
||||
{
|
||||
if (action is null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var content = new Composer();
|
||||
action(content);
|
||||
_root.Append(new ForegroundElement(color, content));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Composer Background(Color color, Action<Composer> action)
|
||||
{
|
||||
if (action is null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var content = new Composer();
|
||||
action(content);
|
||||
_root.Append(new BackgroundElement(color, content));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Composer Style(Styles style, Action<Composer> action)
|
||||
{
|
||||
if (action is null)
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
var content = new Composer();
|
||||
action(content);
|
||||
_root.Append(new StyleElement(style, content));
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
_root.Render(renderer);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
|
||||
internal sealed class BackgroundElement : IRenderable
|
||||
{
|
||||
private readonly Color _color;
|
||||
private readonly IRenderable _element;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length => _element.Length;
|
||||
|
||||
public BackgroundElement(Color color, IRenderable element)
|
||||
{
|
||||
_color = color;
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
if (renderer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderer));
|
||||
}
|
||||
|
||||
using (renderer.PushColor(_color, foreground: false))
|
||||
{
|
||||
_element.Render(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
|
||||
internal sealed class BlockElement : IRenderable
|
||||
{
|
||||
private readonly List<IRenderable> _elements;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length { get; private set; }
|
||||
|
||||
public IReadOnlyList<IRenderable> Elements => _elements;
|
||||
|
||||
public BlockElement()
|
||||
{
|
||||
_elements = new List<IRenderable>();
|
||||
}
|
||||
|
||||
public BlockElement Append(IRenderable element)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
_elements.Add(element);
|
||||
Length += element.Length;
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
foreach (var element in _elements)
|
||||
{
|
||||
element.Render(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
|
||||
internal sealed class ForegroundElement : IRenderable
|
||||
{
|
||||
private readonly Color _color;
|
||||
private readonly IRenderable _element;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length => _element.Length;
|
||||
|
||||
public ForegroundElement(Color color, IRenderable element)
|
||||
{
|
||||
_color = color;
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
if (renderer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderer));
|
||||
}
|
||||
|
||||
using (renderer.PushColor(_color, foreground: true))
|
||||
{
|
||||
_element.Render(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,18 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
|
||||
internal sealed class LineBreakElement : IRenderable
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
public int Length => 0;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
renderer.Write(Environment.NewLine);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
|
||||
internal sealed class StyleElement : IRenderable
|
||||
{
|
||||
private readonly Styles _style;
|
||||
private readonly IRenderable _element;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length => _element.Length;
|
||||
|
||||
public StyleElement(Styles style, IRenderable element)
|
||||
{
|
||||
_style = style;
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
if (renderer is null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(renderer));
|
||||
}
|
||||
|
||||
using (renderer.PushStyle(_style))
|
||||
{
|
||||
_element.Render(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,24 +0,0 @@
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
[SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")]
|
||||
internal sealed class TextElement : IRenderable
|
||||
{
|
||||
private readonly string _text;
|
||||
|
||||
/// <inheritdoc/>
|
||||
public int Length => _text.Length;
|
||||
|
||||
public TextElement(string text)
|
||||
{
|
||||
_text = text ?? throw new System.ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
renderer.Write(_text);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,19 +0,0 @@
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents something that can be rendered to a console.
|
||||
/// </summary>
|
||||
internal interface IRenderable
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the length of the element.
|
||||
/// </summary>
|
||||
int Length { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Renders the element using the specified renderer.
|
||||
/// </summary>
|
||||
/// <param name="console">The renderer to use.</param>
|
||||
void Render(IAnsiConsole console);
|
||||
}
|
||||
}
|
@ -1,7 +1,6 @@
|
||||
using System;
|
||||
using Spectre.Console.Internal;
|
||||
|
||||
namespace Spectre.Console
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class ConsoleBuilder
|
||||
{
|
||||
|
@ -12,7 +12,7 @@ namespace Spectre.Console.Internal
|
||||
throw new ArgumentNullException(nameof(console));
|
||||
}
|
||||
|
||||
var current = console.Foreground;
|
||||
var current = foreground ? console.Foreground : console.Background;
|
||||
console.SetColor(color, foreground);
|
||||
return new ColorScope(console, current, foreground);
|
||||
}
|
||||
|
48
src/Spectre.Console/Internal/Lookup.cs
Normal file
48
src/Spectre.Console/Internal/Lookup.cs
Normal file
@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class Lookup
|
||||
{
|
||||
private readonly Dictionary<string, Styles?> _styles;
|
||||
private readonly Dictionary<string, Color?> _colors;
|
||||
|
||||
private static readonly Lazy<Lookup> _lazy = new Lazy<Lookup>(() => new Lookup());
|
||||
public static Lookup Instance => _lazy.Value;
|
||||
|
||||
private Lookup()
|
||||
{
|
||||
_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 },
|
||||
};
|
||||
|
||||
_colors = new Dictionary<string, Color?>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var color in ColorPalette.EightBit)
|
||||
{
|
||||
_colors.Add(color.Name, color);
|
||||
}
|
||||
}
|
||||
|
||||
public Styles? GetStyle(string name)
|
||||
{
|
||||
_styles.TryGetValue(name, out var style);
|
||||
return style;
|
||||
}
|
||||
|
||||
public Color? GetColor(string name)
|
||||
{
|
||||
_colors.TryGetValue(name, out var color);
|
||||
return color;
|
||||
}
|
||||
}
|
||||
}
|
30
src/Spectre.Console/Internal/Markup/Ast/MarkupBlockNode.cs
Normal file
30
src/Spectre.Console/Internal/Markup/Ast/MarkupBlockNode.cs
Normal file
@ -0,0 +1,30 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class MarkupBlockNode : IMarkupNode
|
||||
{
|
||||
private readonly List<IMarkupNode> _elements;
|
||||
|
||||
public MarkupBlockNode()
|
||||
{
|
||||
_elements = new List<IMarkupNode>();
|
||||
}
|
||||
|
||||
public void Append(IMarkupNode element)
|
||||
{
|
||||
if (element != null)
|
||||
{
|
||||
_elements.Add(element);
|
||||
}
|
||||
}
|
||||
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
foreach (var element in _elements)
|
||||
{
|
||||
element.Render(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
52
src/Spectre.Console/Internal/Markup/Ast/MarkupStyleNode.cs
Normal file
52
src/Spectre.Console/Internal/Markup/Ast/MarkupStyleNode.cs
Normal file
@ -0,0 +1,52 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class MarkupStyleNode : IMarkupNode
|
||||
{
|
||||
private readonly Styles? _style;
|
||||
private readonly Color? _foreground;
|
||||
private readonly Color? _background;
|
||||
private readonly IMarkupNode _element;
|
||||
|
||||
public MarkupStyleNode(
|
||||
Styles? style,
|
||||
Color? foreground,
|
||||
Color? background,
|
||||
IMarkupNode element)
|
||||
{
|
||||
_style = style;
|
||||
_foreground = foreground;
|
||||
_background = background;
|
||||
_element = element ?? throw new ArgumentNullException(nameof(element));
|
||||
}
|
||||
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
var style = (IDisposable)null;
|
||||
var foreground = (IDisposable)null;
|
||||
var background = (IDisposable)null;
|
||||
|
||||
if (_style != null)
|
||||
{
|
||||
style = renderer.PushStyle(_style.Value);
|
||||
}
|
||||
|
||||
if (_foreground != null)
|
||||
{
|
||||
foreground = renderer.PushColor(_foreground.Value, foreground: true);
|
||||
}
|
||||
|
||||
if (_background != null)
|
||||
{
|
||||
background = renderer.PushColor(_background.Value, foreground: false);
|
||||
}
|
||||
|
||||
_element.Render(renderer);
|
||||
|
||||
background?.Dispose();
|
||||
foreground?.Dispose();
|
||||
style?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
19
src/Spectre.Console/Internal/Markup/Ast/MarkupTextNode.cs
Normal file
19
src/Spectre.Console/Internal/Markup/Ast/MarkupTextNode.cs
Normal file
@ -0,0 +1,19 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class MarkupTextNode : IMarkupNode
|
||||
{
|
||||
public string Text { get; }
|
||||
|
||||
public MarkupTextNode(string text)
|
||||
{
|
||||
Text = text ?? throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
public void Render(IAnsiConsole renderer)
|
||||
{
|
||||
renderer.Write(Text);
|
||||
}
|
||||
}
|
||||
}
|
14
src/Spectre.Console/Internal/Markup/IMarkupNode.cs
Normal file
14
src/Spectre.Console/Internal/Markup/IMarkupNode.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a parsed markup node.
|
||||
/// </summary>
|
||||
internal interface IMarkupNode
|
||||
{
|
||||
/// <summary>
|
||||
/// Renders the node using the specified renderer.
|
||||
/// </summary>
|
||||
/// <param name="renderer">The renderer to use.</param>
|
||||
void Render(IAnsiConsole renderer);
|
||||
}
|
||||
}
|
72
src/Spectre.Console/Internal/Markup/MarkupParser.cs
Normal file
72
src/Spectre.Console/Internal/Markup/MarkupParser.cs
Normal file
@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal static class MarkupParser
|
||||
{
|
||||
public static IMarkupNode Parse(string text)
|
||||
{
|
||||
using var tokenizer = new MarkupTokenizer(text);
|
||||
var root = new MarkupBlockNode();
|
||||
|
||||
var stack = new Stack<MarkupBlockNode>();
|
||||
var current = root;
|
||||
|
||||
while (true)
|
||||
{
|
||||
var token = tokenizer.GetNext();
|
||||
if (token == null)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
if (token.Kind == MarkupTokenKind.Text)
|
||||
{
|
||||
current.Append(new MarkupTextNode(token.Value));
|
||||
continue;
|
||||
}
|
||||
else if (token.Kind == MarkupTokenKind.Open)
|
||||
{
|
||||
var (style, foreground, background) = MarkupStyleParser.Parse(token.Value);
|
||||
var content = new MarkupBlockNode();
|
||||
current.Append(new MarkupStyleNode(style, foreground, background, content));
|
||||
|
||||
current = content;
|
||||
stack.Push(current);
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (token.Kind == MarkupTokenKind.Close)
|
||||
{
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
throw new InvalidOperationException($"Encountered closing tag when none was expected near position {token.Position}.");
|
||||
}
|
||||
|
||||
stack.Pop();
|
||||
|
||||
if (stack.Count == 0)
|
||||
{
|
||||
current = root;
|
||||
}
|
||||
else
|
||||
{
|
||||
current = stack.Peek();
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new InvalidOperationException("Encountered unkown markup token.");
|
||||
}
|
||||
|
||||
if (stack.Count > 0)
|
||||
{
|
||||
throw new InvalidOperationException("Unbalanced markup stack. Did you forget to close a tag?");
|
||||
}
|
||||
|
||||
return root;
|
||||
}
|
||||
}
|
||||
}
|
65
src/Spectre.Console/Internal/Markup/MarkupStyleParser.cs
Normal file
65
src/Spectre.Console/Internal/Markup/MarkupStyleParser.cs
Normal file
@ -0,0 +1,65 @@
|
||||
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 = Lookup.Instance.GetStyle(part);
|
||||
if (style != null)
|
||||
{
|
||||
if (effectiveStyle == null)
|
||||
{
|
||||
effectiveStyle = Styles.None;
|
||||
}
|
||||
|
||||
effectiveStyle |= style.Value;
|
||||
}
|
||||
else
|
||||
{
|
||||
var color = Lookup.Instance.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);
|
||||
}
|
||||
}
|
||||
}
|
18
src/Spectre.Console/Internal/Markup/MarkupToken.cs
Normal file
18
src/Spectre.Console/Internal/Markup/MarkupToken.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using System;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class MarkupToken
|
||||
{
|
||||
public MarkupTokenKind Kind { get; }
|
||||
public string Value { get; }
|
||||
public int Position { get; set; }
|
||||
|
||||
public MarkupToken(MarkupTokenKind kind, string value, int position)
|
||||
{
|
||||
Kind = kind;
|
||||
Value = value ?? throw new ArgumentNullException(nameof(value));
|
||||
Position = position;
|
||||
}
|
||||
}
|
||||
}
|
9
src/Spectre.Console/Internal/Markup/MarkupTokenKind.cs
Normal file
9
src/Spectre.Console/Internal/Markup/MarkupTokenKind.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal enum MarkupTokenKind
|
||||
{
|
||||
Text = 0,
|
||||
Open,
|
||||
Close,
|
||||
}
|
||||
}
|
104
src/Spectre.Console/Internal/Markup/MarkupTokenizer.cs
Normal file
104
src/Spectre.Console/Internal/Markup/MarkupTokenizer.cs
Normal file
@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class MarkupTokenizer : IDisposable
|
||||
{
|
||||
private readonly StringBuffer _reader;
|
||||
|
||||
public MarkupTokenizer(string text)
|
||||
{
|
||||
_reader = new StringBuffer(text ?? throw new ArgumentNullException(nameof(text)));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_reader.Dispose();
|
||||
}
|
||||
|
||||
public MarkupToken GetNext()
|
||||
{
|
||||
if (_reader.Eof)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var current = _reader.Peek();
|
||||
if (current == '[')
|
||||
{
|
||||
var position = _reader.Position;
|
||||
|
||||
_reader.Read();
|
||||
|
||||
if (_reader.Eof)
|
||||
{
|
||||
throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}.");
|
||||
}
|
||||
|
||||
current = _reader.Peek();
|
||||
if (current == '[')
|
||||
{
|
||||
_reader.Read();
|
||||
return new MarkupToken(MarkupTokenKind.Text, "[", position);
|
||||
}
|
||||
|
||||
if (current == '/')
|
||||
{
|
||||
_reader.Read();
|
||||
|
||||
if (_reader.Eof)
|
||||
{
|
||||
throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}.");
|
||||
}
|
||||
|
||||
current = _reader.Peek();
|
||||
if (current != ']')
|
||||
{
|
||||
throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}.");
|
||||
}
|
||||
|
||||
_reader.Read();
|
||||
return new MarkupToken(MarkupTokenKind.Close, string.Empty, position);
|
||||
}
|
||||
|
||||
var builder = new StringBuilder();
|
||||
while (!_reader.Eof)
|
||||
{
|
||||
current = _reader.Peek();
|
||||
if (current == ']')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
builder.Append(_reader.Read());
|
||||
}
|
||||
|
||||
if (_reader.Eof)
|
||||
{
|
||||
throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}.");
|
||||
}
|
||||
|
||||
_reader.Read();
|
||||
return new MarkupToken(MarkupTokenKind.Open, builder.ToString(), position);
|
||||
}
|
||||
else
|
||||
{
|
||||
var position = _reader.Position;
|
||||
var builder = new StringBuilder();
|
||||
while (!_reader.Eof)
|
||||
{
|
||||
current = _reader.Peek();
|
||||
if (current == '[')
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
builder.Append(_reader.Read());
|
||||
}
|
||||
|
||||
return new MarkupToken(MarkupTokenKind.Text, builder.ToString(), position);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
50
src/Spectre.Console/Internal/Markup/StringBuffer.cs
Normal file
50
src/Spectre.Console/Internal/Markup/StringBuffer.cs
Normal file
@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Spectre.Console.Internal
|
||||
{
|
||||
internal sealed class StringBuffer : IDisposable
|
||||
{
|
||||
private readonly StringReader _reader;
|
||||
private readonly int _length;
|
||||
|
||||
public int Position { get; private set; }
|
||||
public bool Eof => Position >= _length;
|
||||
|
||||
public StringBuffer(string text)
|
||||
{
|
||||
text ??= string.Empty;
|
||||
|
||||
_reader = new StringReader(text);
|
||||
_length = text.Length;
|
||||
|
||||
Position = 0;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_reader.Dispose();
|
||||
}
|
||||
|
||||
public char Peek()
|
||||
{
|
||||
if (Eof)
|
||||
{
|
||||
throw new InvalidOperationException("Tried to peek past the end of the text.");
|
||||
}
|
||||
|
||||
return (char)_reader.Peek();
|
||||
}
|
||||
|
||||
public char Read()
|
||||
{
|
||||
if (Eof)
|
||||
{
|
||||
throw new InvalidOperationException("Tried to read past the end of the text.");
|
||||
}
|
||||
|
||||
Position++;
|
||||
return (char)_reader.Read();
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user