mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-06-20 21:58:16 +08:00
Preparations for the 1.0 release
* Less cluttered solution layout. * Move examples to a repository of its own. * Move Roslyn analyzer to a repository of its own. * Enable central package management. * Clean up csproj files. * Add README file to NuGet packages.
This commit is contained in:

committed by
Patrik Svensson

parent
bb72b44d60
commit
42fd801876
14
src/Extensions/Spectre.Console.Json/IJsonParser.cs
Normal file
14
src/Extensions/Spectre.Console.Json/IJsonParser.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a JSON parser.
|
||||
/// </summary>
|
||||
public interface IJsonParser
|
||||
{
|
||||
/// <summary>
|
||||
/// Parses the provided JSON into an abstract syntax tree.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON to parse.</param>
|
||||
/// <returns>An <see cref="JsonSyntax"/> instance.</returns>
|
||||
JsonSyntax Parse(string json);
|
||||
}
|
101
src/Extensions/Spectre.Console.Json/JsonBuilder.cs
Normal file
101
src/Extensions/Spectre.Console.Json/JsonBuilder.cs
Normal file
@ -0,0 +1,101 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
internal sealed class JsonBuilderContext
|
||||
{
|
||||
public Paragraph Paragraph { get; }
|
||||
public int Indentation { get; set; }
|
||||
public JsonTextStyles Styling { get; }
|
||||
|
||||
public JsonBuilderContext(JsonTextStyles styling)
|
||||
{
|
||||
Paragraph = new Paragraph();
|
||||
Styling = styling;
|
||||
}
|
||||
|
||||
public void InsertIndentation()
|
||||
{
|
||||
Paragraph.Append(new string(' ', Indentation * 3));
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class JsonBuilder : JsonSyntaxVisitor<JsonBuilderContext>
|
||||
{
|
||||
public static JsonBuilder Shared { get; } = new JsonBuilder();
|
||||
|
||||
public override void VisitObject(JsonObject syntax, JsonBuilderContext context)
|
||||
{
|
||||
context.Paragraph.Append("{", context.Styling.BracesStyle);
|
||||
context.Paragraph.Append("\n");
|
||||
context.Indentation++;
|
||||
|
||||
foreach (var (_, _, last, property) in syntax.Members.Enumerate())
|
||||
{
|
||||
context.InsertIndentation();
|
||||
property.Accept(this, context);
|
||||
|
||||
if (!last)
|
||||
{
|
||||
context.Paragraph.Append(",", context.Styling.CommaStyle);
|
||||
}
|
||||
|
||||
context.Paragraph.Append("\n");
|
||||
}
|
||||
|
||||
context.Indentation--;
|
||||
context.InsertIndentation();
|
||||
context.Paragraph.Append("}", context.Styling.BracesStyle);
|
||||
}
|
||||
|
||||
public override void VisitArray(JsonArray syntax, JsonBuilderContext context)
|
||||
{
|
||||
context.Paragraph.Append("[", context.Styling.BracketsStyle);
|
||||
context.Paragraph.Append("\n");
|
||||
context.Indentation++;
|
||||
|
||||
foreach (var (_, _, last, item) in syntax.Items.Enumerate())
|
||||
{
|
||||
context.InsertIndentation();
|
||||
item.Accept(this, context);
|
||||
|
||||
if (!last)
|
||||
{
|
||||
context.Paragraph.Append(",", context.Styling.CommaStyle);
|
||||
}
|
||||
|
||||
context.Paragraph.Append("\n");
|
||||
}
|
||||
|
||||
context.Indentation--;
|
||||
context.InsertIndentation();
|
||||
context.Paragraph.Append("]", context.Styling.BracketsStyle);
|
||||
}
|
||||
|
||||
public override void VisitMember(JsonMember syntax, JsonBuilderContext context)
|
||||
{
|
||||
context.Paragraph.Append(syntax.Name, context.Styling.MemberStyle);
|
||||
context.Paragraph.Append(":", context.Styling.ColonStyle);
|
||||
context.Paragraph.Append(" ");
|
||||
|
||||
syntax.Value.Accept(this, context);
|
||||
}
|
||||
|
||||
public override void VisitNumber(JsonNumber syntax, JsonBuilderContext context)
|
||||
{
|
||||
context.Paragraph.Append(syntax.Lexeme, context.Styling.NumberStyle);
|
||||
}
|
||||
|
||||
public override void VisitString(JsonString syntax, JsonBuilderContext context)
|
||||
{
|
||||
context.Paragraph.Append(syntax.Lexeme, context.Styling.StringStyle);
|
||||
}
|
||||
|
||||
public override void VisitBoolean(JsonBoolean syntax, JsonBuilderContext context)
|
||||
{
|
||||
context.Paragraph.Append(syntax.Lexeme, context.Styling.BooleanStyle);
|
||||
}
|
||||
|
||||
public override void VisitNull(JsonNull syntax, JsonBuilderContext context)
|
||||
{
|
||||
context.Paragraph.Append(syntax.Lexeme, context.Styling.NullStyle);
|
||||
}
|
||||
}
|
146
src/Extensions/Spectre.Console.Json/JsonParser.cs
Normal file
146
src/Extensions/Spectre.Console.Json/JsonParser.cs
Normal file
@ -0,0 +1,146 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
internal sealed class JsonParser : IJsonParser
|
||||
{
|
||||
public static JsonParser Shared { get; } = new JsonParser();
|
||||
|
||||
public JsonSyntax Parse(string json)
|
||||
{
|
||||
try
|
||||
{
|
||||
var tokens = JsonTokenizer.Tokenize(json);
|
||||
var reader = new JsonTokenReader(tokens);
|
||||
return ParseElement(reader);
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new InvalidOperationException("Invalid JSON");
|
||||
}
|
||||
}
|
||||
|
||||
private static JsonSyntax ParseElement(JsonTokenReader reader)
|
||||
{
|
||||
return ParseValue(reader);
|
||||
}
|
||||
|
||||
private static List<JsonSyntax> ParseElements(JsonTokenReader reader)
|
||||
{
|
||||
var members = new List<JsonSyntax>();
|
||||
|
||||
while (!reader.Eof)
|
||||
{
|
||||
members.Add(ParseElement(reader));
|
||||
|
||||
if (reader.Peek()?.Type != JsonTokenType.Comma)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
reader.Consume(JsonTokenType.Comma);
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
private static JsonSyntax ParseValue(JsonTokenReader reader)
|
||||
{
|
||||
var current = reader.Peek();
|
||||
if (current == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not parse value (EOF)");
|
||||
}
|
||||
|
||||
if (current.Type == JsonTokenType.LeftBrace)
|
||||
{
|
||||
return ParseObject(reader);
|
||||
}
|
||||
|
||||
if (current.Type == JsonTokenType.LeftBracket)
|
||||
{
|
||||
return ParseArray(reader);
|
||||
}
|
||||
|
||||
if (current.Type == JsonTokenType.Number)
|
||||
{
|
||||
reader.Consume(JsonTokenType.Number);
|
||||
return new JsonNumber(current.Lexeme);
|
||||
}
|
||||
|
||||
if (current.Type == JsonTokenType.String)
|
||||
{
|
||||
reader.Consume(JsonTokenType.String);
|
||||
return new JsonString(current.Lexeme);
|
||||
}
|
||||
|
||||
if (current.Type == JsonTokenType.Boolean)
|
||||
{
|
||||
reader.Consume(JsonTokenType.Boolean);
|
||||
return new JsonBoolean(current.Lexeme);
|
||||
}
|
||||
|
||||
if (current.Type == JsonTokenType.Null)
|
||||
{
|
||||
reader.Consume(JsonTokenType.Null);
|
||||
return new JsonNull(current.Lexeme);
|
||||
}
|
||||
|
||||
throw new InvalidOperationException($"Unknown value token: {current.Type}");
|
||||
}
|
||||
|
||||
private static JsonSyntax ParseObject(JsonTokenReader reader)
|
||||
{
|
||||
reader.Consume(JsonTokenType.LeftBrace);
|
||||
|
||||
var result = new JsonObject();
|
||||
|
||||
if (reader.Peek()?.Type != JsonTokenType.RightBrace)
|
||||
{
|
||||
result.Members.AddRange(ParseMembers(reader));
|
||||
}
|
||||
|
||||
reader.Consume(JsonTokenType.RightBrace);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static JsonSyntax ParseArray(JsonTokenReader reader)
|
||||
{
|
||||
reader.Consume(JsonTokenType.LeftBracket);
|
||||
|
||||
var result = new JsonArray();
|
||||
|
||||
if (reader.Peek()?.Type != JsonTokenType.RightBracket)
|
||||
{
|
||||
result.Items.AddRange(ParseElements(reader));
|
||||
}
|
||||
|
||||
reader.Consume(JsonTokenType.RightBracket);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static List<JsonMember> ParseMembers(JsonTokenReader reader)
|
||||
{
|
||||
var members = new List<JsonMember>();
|
||||
|
||||
while (!reader.Eof)
|
||||
{
|
||||
members.Add(ParseMember(reader));
|
||||
|
||||
if (reader.Peek()?.Type != JsonTokenType.Comma)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
reader.Consume(JsonTokenType.Comma);
|
||||
}
|
||||
|
||||
return members;
|
||||
}
|
||||
|
||||
private static JsonMember ParseMember(JsonTokenReader reader)
|
||||
{
|
||||
var name = reader.Consume(JsonTokenType.String);
|
||||
reader.Consume(JsonTokenType.Colon);
|
||||
var value = ParseElement(reader);
|
||||
return new JsonMember(name.Lexeme, value);
|
||||
}
|
||||
}
|
106
src/Extensions/Spectre.Console.Json/JsonText.cs
Normal file
106
src/Extensions/Spectre.Console.Json/JsonText.cs
Normal file
@ -0,0 +1,106 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
/// <summary>
|
||||
/// A renderable piece of JSON text.
|
||||
/// </summary>
|
||||
public sealed class JsonText : JustInTimeRenderable
|
||||
{
|
||||
private readonly string _json;
|
||||
private JsonSyntax? _syntax;
|
||||
private IJsonParser? _parser;
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for braces.
|
||||
/// </summary>
|
||||
public Style? BracesStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for brackets.
|
||||
/// </summary>
|
||||
public Style? BracketsStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for member names.
|
||||
/// </summary>
|
||||
public Style? MemberStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for colons.
|
||||
/// </summary>
|
||||
public Style? ColonStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for commas.
|
||||
/// </summary>
|
||||
public Style? CommaStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for string literals.
|
||||
/// </summary>
|
||||
public Style? StringStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for number literals.
|
||||
/// </summary>
|
||||
public Style? NumberStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for boolean literals.
|
||||
/// </summary>
|
||||
public Style? BooleanStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the style used for <c>null</c> literals.
|
||||
/// </summary>
|
||||
public Style? NullStyle { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the JSON parser.
|
||||
/// </summary>
|
||||
public IJsonParser? Parser
|
||||
{
|
||||
get
|
||||
{
|
||||
return _parser;
|
||||
}
|
||||
set
|
||||
{
|
||||
_syntax = null;
|
||||
_parser = value;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonText"/> class.
|
||||
/// </summary>
|
||||
/// <param name="json">The JSON to render.</param>
|
||||
public JsonText(string json)
|
||||
{
|
||||
_json = json ?? throw new ArgumentNullException(nameof(json));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override IRenderable Build()
|
||||
{
|
||||
if (_syntax == null)
|
||||
{
|
||||
_syntax = (Parser ?? JsonParser.Shared).Parse(_json);
|
||||
}
|
||||
|
||||
var context = new JsonBuilderContext(new JsonTextStyles
|
||||
{
|
||||
BracesStyle = BracesStyle ?? Color.Grey,
|
||||
BracketsStyle = BracketsStyle ?? Color.Grey,
|
||||
MemberStyle = MemberStyle ?? Color.Blue,
|
||||
ColonStyle = ColonStyle ?? Color.Yellow,
|
||||
CommaStyle = CommaStyle ?? Color.Grey,
|
||||
StringStyle = StringStyle ?? Color.Red,
|
||||
NumberStyle = NumberStyle ?? Color.Green,
|
||||
BooleanStyle = BooleanStyle ?? Color.Green,
|
||||
NullStyle = NullStyle ?? Color.Grey,
|
||||
});
|
||||
|
||||
_syntax.Accept(JsonBuilder.Shared, context);
|
||||
return context.Paragraph;
|
||||
}
|
||||
}
|
313
src/Extensions/Spectre.Console.Json/JsonTextExtensions.cs
Normal file
313
src/Extensions/Spectre.Console.Json/JsonTextExtensions.cs
Normal file
@ -0,0 +1,313 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
/// <summary>
|
||||
/// Contains extension methods for <see cref="JsonText"/>.
|
||||
/// </summary>
|
||||
public static class JsonTextExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Sets the style used for braces.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText BracesStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.BracesStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the style used for brackets.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText BracketStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.BracketsStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the style used for member names.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText MemberStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.MemberStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the style used for colons.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText ColonStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.ColonStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the style used for commas.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText CommaStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.CommaStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the style used for string literals.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText StringStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.StringStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the style used for number literals.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText NumberStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.NumberStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the style used for boolean literals.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText BooleanStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.BooleanStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the style used for <c>null</c> literals.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="style">The style to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText NullStyle(this JsonText text, Style? style)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.NullStyle = style;
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for braces.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText BracesColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.BracesStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for brackets.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText BracketColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.BracketsStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for member names.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText MemberColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.MemberStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for colons.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText ColonColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.ColonStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for commas.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText CommaColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.CommaStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for string literals.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText StringColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.StringStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for number literals.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText NumberColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.NumberStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for boolean literals.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText BooleanColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.BooleanStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the color used for <c>null</c> literals.
|
||||
/// </summary>
|
||||
/// <param name="text">The JSON text instance.</param>
|
||||
/// <param name="color">The color to set.</param>
|
||||
/// <returns>The same instance so that multiple calls can be chained.</returns>
|
||||
public static JsonText NullColor(this JsonText text, Color color)
|
||||
{
|
||||
if (text == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(text));
|
||||
}
|
||||
|
||||
text.NullStyle = new Style(color);
|
||||
return text;
|
||||
}
|
||||
}
|
14
src/Extensions/Spectre.Console.Json/JsonTextStyles.cs
Normal file
14
src/Extensions/Spectre.Console.Json/JsonTextStyles.cs
Normal file
@ -0,0 +1,14 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
internal sealed class JsonTextStyles
|
||||
{
|
||||
public Style BracesStyle { get; set; } = null!;
|
||||
public Style BracketsStyle { get; set; } = null!;
|
||||
public Style MemberStyle { get; set; } = null!;
|
||||
public Style ColonStyle { get; set; } = null!;
|
||||
public Style CommaStyle { get; set; } = null!;
|
||||
public Style StringStyle { get; set; } = null!;
|
||||
public Style NumberStyle { get; set; } = null!;
|
||||
public Style BooleanStyle { get; set; } = null!;
|
||||
public Style NullStyle { get; set; } = null!;
|
||||
}
|
13
src/Extensions/Spectre.Console.Json/JsonToken.cs
Normal file
13
src/Extensions/Spectre.Console.Json/JsonToken.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
internal sealed class JsonToken
|
||||
{
|
||||
public JsonTokenType Type { get; }
|
||||
public string Lexeme { get; }
|
||||
|
||||
public JsonToken(JsonTokenType type, string lexeme)
|
||||
{
|
||||
Type = type;
|
||||
Lexeme = lexeme ?? throw new ArgumentNullException(nameof(lexeme));
|
||||
}
|
||||
}
|
55
src/Extensions/Spectre.Console.Json/JsonTokenReader.cs
Normal file
55
src/Extensions/Spectre.Console.Json/JsonTokenReader.cs
Normal file
@ -0,0 +1,55 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
internal sealed class JsonTokenReader
|
||||
{
|
||||
private readonly List<JsonToken> _reader;
|
||||
private readonly int _length;
|
||||
|
||||
public int Position { get; private set; }
|
||||
public bool Eof => Position >= _length;
|
||||
|
||||
public JsonTokenReader(List<JsonToken> tokens)
|
||||
{
|
||||
_reader = tokens;
|
||||
_length = tokens.Count;
|
||||
|
||||
Position = 0;
|
||||
}
|
||||
|
||||
public JsonToken Consume(JsonTokenType type)
|
||||
{
|
||||
var read = Read();
|
||||
if (read == null)
|
||||
{
|
||||
throw new InvalidOperationException("Could not read token");
|
||||
}
|
||||
|
||||
if (read.Type != type)
|
||||
{
|
||||
throw new InvalidOperationException($"Expected '{type}' token, but found '{read.Type}'");
|
||||
}
|
||||
|
||||
return read;
|
||||
}
|
||||
|
||||
public JsonToken? Peek()
|
||||
{
|
||||
if (Eof)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return _reader[Position];
|
||||
}
|
||||
|
||||
public JsonToken? Read()
|
||||
{
|
||||
if (Eof)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
Position++;
|
||||
return _reader[Position - 1];
|
||||
}
|
||||
}
|
15
src/Extensions/Spectre.Console.Json/JsonTokenType.cs
Normal file
15
src/Extensions/Spectre.Console.Json/JsonTokenType.cs
Normal file
@ -0,0 +1,15 @@
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
internal enum JsonTokenType
|
||||
{
|
||||
LeftBrace,
|
||||
RightBrace,
|
||||
LeftBracket,
|
||||
RightBracket,
|
||||
Colon,
|
||||
Comma,
|
||||
String,
|
||||
Number,
|
||||
Boolean,
|
||||
Null,
|
||||
}
|
205
src/Extensions/Spectre.Console.Json/JsonTokenizer.cs
Normal file
205
src/Extensions/Spectre.Console.Json/JsonTokenizer.cs
Normal file
@ -0,0 +1,205 @@
|
||||
using System.Text;
|
||||
|
||||
namespace Spectre.Console.Json;
|
||||
|
||||
internal static class JsonTokenizer
|
||||
{
|
||||
private static readonly Dictionary<char, JsonTokenType> _typeLookup;
|
||||
private static readonly Dictionary<string, JsonTokenType> _keywords;
|
||||
private static readonly HashSet<char> _allowedEscapedChars;
|
||||
|
||||
static JsonTokenizer()
|
||||
{
|
||||
_typeLookup = new Dictionary<char, JsonTokenType>
|
||||
{
|
||||
{ '{', JsonTokenType.LeftBrace },
|
||||
{ '}', JsonTokenType.RightBrace },
|
||||
{ '[', JsonTokenType.LeftBracket },
|
||||
{ ']', JsonTokenType.RightBracket },
|
||||
{ ':', JsonTokenType.Colon },
|
||||
{ ',', JsonTokenType.Comma },
|
||||
};
|
||||
|
||||
_keywords = new Dictionary<string, JsonTokenType>
|
||||
{
|
||||
{ "true", JsonTokenType.Boolean },
|
||||
{ "false", JsonTokenType.Boolean },
|
||||
{ "null", JsonTokenType.Null },
|
||||
};
|
||||
|
||||
_allowedEscapedChars = new HashSet<char>
|
||||
{
|
||||
'\"', '\\', '/', 'b', 'f', 'n', 'r', 't', 'u',
|
||||
};
|
||||
}
|
||||
|
||||
public static List<JsonToken> Tokenize(string text)
|
||||
{
|
||||
var result = new List<JsonToken>();
|
||||
var buffer = new StringBuffer(text);
|
||||
|
||||
while (!buffer.Eof)
|
||||
{
|
||||
var current = buffer.Peek();
|
||||
|
||||
if (_typeLookup.TryGetValue(current, out var tokenType))
|
||||
{
|
||||
buffer.Read(); // Consume
|
||||
result.Add(new JsonToken(tokenType, current.ToString()));
|
||||
continue;
|
||||
}
|
||||
else if (current == '\"')
|
||||
{
|
||||
result.Add(ReadString(buffer));
|
||||
}
|
||||
else if (current == '-' || current.IsDigit())
|
||||
{
|
||||
result.Add(ReadNumber(buffer));
|
||||
}
|
||||
else if (current is ' ' or '\n' or '\r' or '\t')
|
||||
{
|
||||
buffer.Read(); // Consume
|
||||
}
|
||||
else if (char.IsLetter(current))
|
||||
{
|
||||
var accumulator = new StringBuilder();
|
||||
while (!buffer.Eof)
|
||||
{
|
||||
current = buffer.Peek();
|
||||
if (!char.IsLetter(current))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Read(); // Consume
|
||||
accumulator.Append(current);
|
||||
}
|
||||
|
||||
if (!_keywords.TryGetValue(accumulator.ToString(), out var keyword))
|
||||
{
|
||||
throw new InvalidOperationException($"Encountered invalid keyword '{keyword}'");
|
||||
}
|
||||
|
||||
result.Add(new JsonToken(keyword, accumulator.ToString()));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Invalid token");
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private static JsonToken ReadString(StringBuffer buffer)
|
||||
{
|
||||
var accumulator = new StringBuilder();
|
||||
accumulator.Append(buffer.Expect('\"'));
|
||||
|
||||
while (!buffer.Eof)
|
||||
{
|
||||
var current = buffer.Peek();
|
||||
if (current == '\"')
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (current == '\\')
|
||||
{
|
||||
buffer.Expect('\\');
|
||||
|
||||
if (buffer.Eof)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
current = buffer.Read();
|
||||
if (!_allowedEscapedChars.Contains(current))
|
||||
{
|
||||
throw new InvalidOperationException("Invalid escape encountered");
|
||||
}
|
||||
|
||||
accumulator.Append('\\').Append(current);
|
||||
}
|
||||
else
|
||||
{
|
||||
accumulator.Append(current);
|
||||
buffer.Read();
|
||||
}
|
||||
}
|
||||
|
||||
if (buffer.Eof)
|
||||
{
|
||||
throw new InvalidOperationException("Unterminated string literal");
|
||||
}
|
||||
|
||||
accumulator.Append(buffer.Expect('\"'));
|
||||
return new JsonToken(JsonTokenType.String, accumulator.ToString());
|
||||
}
|
||||
|
||||
private static JsonToken ReadNumber(StringBuffer buffer)
|
||||
{
|
||||
var accumulator = new StringBuilder();
|
||||
|
||||
// Minus?
|
||||
if (buffer.Peek() == '-')
|
||||
{
|
||||
buffer.Read();
|
||||
accumulator.Append("-");
|
||||
}
|
||||
|
||||
// Digits
|
||||
var current = buffer.Peek();
|
||||
if (current.IsDigit(min: 1))
|
||||
{
|
||||
ReadDigits(buffer, accumulator, min: 1);
|
||||
}
|
||||
else if (current == '0')
|
||||
{
|
||||
accumulator.Append(buffer.Expect('0'));
|
||||
}
|
||||
else
|
||||
{
|
||||
throw new InvalidOperationException("Invalid number");
|
||||
}
|
||||
|
||||
// Fractions
|
||||
current = buffer.Peek();
|
||||
if (current == '.')
|
||||
{
|
||||
accumulator.Append(buffer.Expect('.'));
|
||||
ReadDigits(buffer, accumulator);
|
||||
}
|
||||
|
||||
// Exponent
|
||||
current = buffer.Peek();
|
||||
if (current is 'e' or 'E')
|
||||
{
|
||||
accumulator.Append(buffer.Read());
|
||||
|
||||
current = buffer.Peek();
|
||||
if (current is '+' or '-')
|
||||
{
|
||||
accumulator.Append(buffer.Read());
|
||||
}
|
||||
|
||||
ReadDigits(buffer, accumulator);
|
||||
}
|
||||
|
||||
return new JsonToken(JsonTokenType.Number, accumulator.ToString());
|
||||
}
|
||||
|
||||
private static void ReadDigits(StringBuffer buffer, StringBuilder accumulator, int min = 0)
|
||||
{
|
||||
while (!buffer.Eof)
|
||||
{
|
||||
var current = buffer.Peek();
|
||||
if (!current.IsDigit(min))
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
buffer.Read(); // Consume
|
||||
accumulator.Append(current);
|
||||
}
|
||||
}
|
||||
}
|
4
src/Extensions/Spectre.Console.Json/Properties/Usings.cs
Normal file
4
src/Extensions/Spectre.Console.Json/Properties/Usings.cs
Normal file
@ -0,0 +1,4 @@
|
||||
global using System.Text;
|
||||
global using Spectre.Console.Internal;
|
||||
global using Spectre.Console.Json.Syntax;
|
||||
global using Spectre.Console.Rendering;
|
@ -0,0 +1,20 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFrameworks>net8.0;net7.0;net6.0;netstandard2.0</TargetFrameworks>
|
||||
<ImplicitUsings>true</ImplicitUsings>
|
||||
<IsPackable>true</IsPackable>
|
||||
<Description>A library that extends Spectre.Console with JSON superpowers.</Description>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Compile Include="..\..\Spectre.Console\Internal\Extensions\CharExtensions.cs" Link="Internal\CharExtensions.cs" />
|
||||
<Compile Include="..\..\Spectre.Console\Internal\Extensions\EnumerableExtensions.cs" Link="Internal\EnumerableExtensions.cs" />
|
||||
<Compile Include="..\..\Spectre.Console\Internal\Text\StringBuffer.cs" Link="Internal\StringBuffer.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\..\Spectre.Console\Spectre.Console.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
25
src/Extensions/Spectre.Console.Json/Syntax/JsonArray.cs
Normal file
25
src/Extensions/Spectre.Console.Json/Syntax/JsonArray.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an array in the JSON abstract syntax tree.
|
||||
/// </summary>
|
||||
public sealed class JsonArray : JsonSyntax
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the array items.
|
||||
/// </summary>
|
||||
public List<JsonSyntax> Items { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonArray"/> class.
|
||||
/// </summary>
|
||||
public JsonArray()
|
||||
{
|
||||
Items = new List<JsonSyntax>();
|
||||
}
|
||||
|
||||
internal override void Accept<T>(JsonSyntaxVisitor<T> visitor, T context)
|
||||
{
|
||||
visitor.VisitArray(this, context);
|
||||
}
|
||||
}
|
26
src/Extensions/Spectre.Console.Json/Syntax/JsonBoolean.cs
Normal file
26
src/Extensions/Spectre.Console.Json/Syntax/JsonBoolean.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a boolean literal in the JSON abstract syntax tree.
|
||||
/// </summary>
|
||||
public sealed class JsonBoolean : JsonSyntax
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the lexeme.
|
||||
/// </summary>
|
||||
public string Lexeme { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonBoolean"/> class.
|
||||
/// </summary>
|
||||
/// <param name="lexeme">The lexeme.</param>
|
||||
public JsonBoolean(string lexeme)
|
||||
{
|
||||
Lexeme = lexeme;
|
||||
}
|
||||
|
||||
internal override void Accept<T>(JsonSyntaxVisitor<T> visitor, T context)
|
||||
{
|
||||
visitor.VisitBoolean(this, context);
|
||||
}
|
||||
}
|
33
src/Extensions/Spectre.Console.Json/Syntax/JsonMember.cs
Normal file
33
src/Extensions/Spectre.Console.Json/Syntax/JsonMember.cs
Normal file
@ -0,0 +1,33 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a member in the JSON abstract syntax tree.
|
||||
/// </summary>
|
||||
public sealed class JsonMember : JsonSyntax
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the member name.
|
||||
/// </summary>
|
||||
public string Name { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the member value.
|
||||
/// </summary>
|
||||
public JsonSyntax Value { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonMember"/> class.
|
||||
/// </summary>
|
||||
/// <param name="name">The name.</param>
|
||||
/// <param name="value">The value.</param>
|
||||
public JsonMember(string name, JsonSyntax value)
|
||||
{
|
||||
Name = name;
|
||||
Value = value;
|
||||
}
|
||||
|
||||
internal override void Accept<T>(JsonSyntaxVisitor<T> visitor, T context)
|
||||
{
|
||||
visitor.VisitMember(this, context);
|
||||
}
|
||||
}
|
26
src/Extensions/Spectre.Console.Json/Syntax/JsonNull.cs
Normal file
26
src/Extensions/Spectre.Console.Json/Syntax/JsonNull.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a null literal in the JSON abstract syntax tree.
|
||||
/// </summary>
|
||||
public sealed class JsonNull : JsonSyntax
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the lexeme.
|
||||
/// </summary>
|
||||
public string Lexeme { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonNull"/> class.
|
||||
/// </summary>
|
||||
/// <param name="lexeme">The lexeme.</param>
|
||||
public JsonNull(string lexeme)
|
||||
{
|
||||
Lexeme = lexeme;
|
||||
}
|
||||
|
||||
internal override void Accept<T>(JsonSyntaxVisitor<T> visitor, T context)
|
||||
{
|
||||
visitor.VisitNull(this, context);
|
||||
}
|
||||
}
|
16
src/Extensions/Spectre.Console.Json/Syntax/JsonNumber.cs
Normal file
16
src/Extensions/Spectre.Console.Json/Syntax/JsonNumber.cs
Normal file
@ -0,0 +1,16 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
internal sealed class JsonNumber : JsonSyntax
|
||||
{
|
||||
public string Lexeme { get; }
|
||||
|
||||
public JsonNumber(string lexeme)
|
||||
{
|
||||
Lexeme = lexeme;
|
||||
}
|
||||
|
||||
internal override void Accept<T>(JsonSyntaxVisitor<T> visitor, T context)
|
||||
{
|
||||
visitor.VisitNumber(this, context);
|
||||
}
|
||||
}
|
25
src/Extensions/Spectre.Console.Json/Syntax/JsonObject.cs
Normal file
25
src/Extensions/Spectre.Console.Json/Syntax/JsonObject.cs
Normal file
@ -0,0 +1,25 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// Represents an object in the JSON abstract syntax tree.
|
||||
/// </summary>
|
||||
public sealed class JsonObject : JsonSyntax
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the object's members.
|
||||
/// </summary>
|
||||
public List<JsonMember> Members { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonObject"/> class.
|
||||
/// </summary>
|
||||
public JsonObject()
|
||||
{
|
||||
Members = new List<JsonMember>();
|
||||
}
|
||||
|
||||
internal override void Accept<T>(JsonSyntaxVisitor<T> visitor, T context)
|
||||
{
|
||||
visitor.VisitObject(this, context);
|
||||
}
|
||||
}
|
26
src/Extensions/Spectre.Console.Json/Syntax/JsonString.cs
Normal file
26
src/Extensions/Spectre.Console.Json/Syntax/JsonString.cs
Normal file
@ -0,0 +1,26 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a string literal in the JSON abstract syntax tree.
|
||||
/// </summary>
|
||||
public sealed class JsonString : JsonSyntax
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets the lexeme.
|
||||
/// </summary>
|
||||
public string Lexeme { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="JsonString"/> class.
|
||||
/// </summary>
|
||||
/// <param name="lexeme">The lexeme.</param>
|
||||
public JsonString(string lexeme)
|
||||
{
|
||||
Lexeme = lexeme;
|
||||
}
|
||||
|
||||
internal override void Accept<T>(JsonSyntaxVisitor<T> visitor, T context)
|
||||
{
|
||||
visitor.VisitString(this, context);
|
||||
}
|
||||
}
|
9
src/Extensions/Spectre.Console.Json/Syntax/JsonSyntax.cs
Normal file
9
src/Extensions/Spectre.Console.Json/Syntax/JsonSyntax.cs
Normal file
@ -0,0 +1,9 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
/// <summary>
|
||||
/// Represents a syntax node in the JSON abstract syntax tree.
|
||||
/// </summary>
|
||||
public abstract class JsonSyntax
|
||||
{
|
||||
internal abstract void Accept<T>(JsonSyntaxVisitor<T> visitor, T context);
|
||||
}
|
@ -0,0 +1,12 @@
|
||||
namespace Spectre.Console.Json.Syntax;
|
||||
|
||||
internal abstract class JsonSyntaxVisitor<T>
|
||||
{
|
||||
public abstract void VisitObject(JsonObject syntax, T context);
|
||||
public abstract void VisitArray(JsonArray syntax, T context);
|
||||
public abstract void VisitMember(JsonMember syntax, T context);
|
||||
public abstract void VisitNumber(JsonNumber syntax, T context);
|
||||
public abstract void VisitString(JsonString syntax, T context);
|
||||
public abstract void VisitBoolean(JsonBoolean syntax, T context);
|
||||
public abstract void VisitNull(JsonNull syntax, T context);
|
||||
}
|
Reference in New Issue
Block a user