mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 08:52:50 +08:00
Add JSON text renderer (#1086)
* Add JsonText widget to render highlighted JSON Closes #1051
This commit is contained in:
parent
3e6e0990c5
commit
54be64ec84
@ -23,7 +23,8 @@ namespace Docs
|
|||||||
{
|
{
|
||||||
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
"../../src/Spectre.Console/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||||
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
"../../src/Spectre.Console.Cli/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||||
"../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
|
"../../src/Spectre.Console.ImageSharp/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs",
|
||||||
|
"../../src/Spectre.Console.Json/**/{!bin,!obj,!packages,!*.Tests,}/**/*.cs"
|
||||||
})
|
})
|
||||||
.AddSetting(Constants.ExampleSourceFiles, new List<string>
|
.AddSetting(Constants.ExampleSourceFiles, new List<string>
|
||||||
{
|
{
|
||||||
|
3
docs/input/assets/casts/json-plain.cast
Normal file
3
docs/input/assets/casts/json-plain.cast
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{"version": 2, "width": 62, "height": 20, "title": "json (plain)", "env": {"TERM": "Spectre.Console"}}
|
||||||
|
[0, "o", "\u001B[37m\u250C\u2500Some JSON in a panel\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[37m{\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[34m\u0022hello\u0022\u001B[0m\u001B[37m:\u001B[0m \u001B[32m32\u001B[0m\u001B[37m,\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[34m\u0022world\u0022\u001B[0m\u001B[37m:\u001B[0m \u001B[37m{\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[34m\u0022foo\u0022\u001B[0m\u001B[37m:\u001B[0m \u001B[32m21\u001B[0m\u001B[37m,\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[34m\u0022bar\u0022\u001B[0m\u001B[37m:\u001B[0m \u001B[32m255\u001B[0m\u001B[37m,\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[34m\u0022baz\u0022\u001B[0m\u001B[37m:\u001B[0m \u001B[37m[\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[32m0.32\u001B[0m\u001B[37m,\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[32m0.33e-32\u001B[0m\u001B[37m,\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[32m0.42e32\u001B[0m\u001B[37m,\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[32m0.55e\u002B32\u001B[0m\u001B[37m,\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[37m{\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[34m\u0022hello\u0022\u001B[0m\u001B[37m:\u001B[0m \u001B[31m\u0022world\u0022\u001B[0m\u001B[37m,\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[34m\u0022lol\u0022\u001B[0m\u001B[37m:\u001B[0m \u001B[37mnull\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[37m}\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[37m]\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[37m}\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2502\u001B[0m \u001B[37m}\u001B[0m \u001B[37m\u2502\u001B[0m\r\n\u001B[37m\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\u001B[0m\r\n"]
|
||||||
|
|
3
docs/input/assets/casts/json-rich.cast
Normal file
3
docs/input/assets/casts/json-rich.cast
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{"version": 2, "width": 62, "height": 20, "title": "json (rich)", "env": {"TERM": "Spectre.Console"}}
|
||||||
|
[0, "o", "\u001B[38;5;11m\u256D\u2500Some JSON in a panel\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256E\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;8m{\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;12m\u0022hello\u0022\u001B[0m\u001B[38;5;11m:\u001B[0m \u001B[38;5;2m32\u001B[0m\u001B[38;5;8m,\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;12m\u0022world\u0022\u001B[0m\u001B[38;5;11m:\u001B[0m \u001B[38;5;8m{\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;12m\u0022foo\u0022\u001B[0m\u001B[38;5;11m:\u001B[0m \u001B[38;5;2m21\u001B[0m\u001B[38;5;8m,\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;12m\u0022bar\u0022\u001B[0m\u001B[38;5;11m:\u001B[0m \u001B[38;5;2m255\u001B[0m\u001B[38;5;8m,\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;12m\u0022baz\u0022\u001B[0m\u001B[38;5;11m:\u001B[0m \u001B[38;5;8m[\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;2m0.32\u001B[0m\u001B[38;5;8m,\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;2m0.33e-32\u001B[0m\u001B[38;5;8m,\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;2m0.42e32\u001B[0m\u001B[38;5;8m,\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;2m0.55e\u002B32\u001B[0m\u001B[38;5;8m,\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;8m{\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;12m\u0022hello\u0022\u001B[0m\u001B[38;5;11m:\u001B[0m \u001B[38;5;9m\u0022world\u0022\u001B[0m\u001B[38;5;8m,\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;12m\u0022lol\u0022\u001B[0m\u001B[38;5;11m:\u001B[0m \u001B[38;5;8mnull\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;8m}\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;8m]\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;8m}\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2502\u001B[0m \u001B[38;5;8m}\u001B[0m \u001B[38;5;11m\u2502\u001B[0m\r\n\u001B[38;5;11m\u2570\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u256F\u001B[0m\r\n"]
|
||||||
|
|
69
docs/input/widgets/json.md
Normal file
69
docs/input/widgets/json.md
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
Title: JSON
|
||||||
|
Order: 70
|
||||||
|
Description: "Use *ImageSharp* to parse images and render them as Ascii art to the console."
|
||||||
|
Reference: T:Spectre.Console.Json.JsonText
|
||||||
|
---
|
||||||
|
|
||||||
|
To add JSON superpowers to
|
||||||
|
your console application to render JSON text, you will need to install
|
||||||
|
the [Spectre.Console.Json](https://www.nuget.org/packages/Spectre.Console.Json) NuGet package.
|
||||||
|
|
||||||
|
```text
|
||||||
|
> dotnet add package Spectre.Console.Json
|
||||||
|
```
|
||||||
|
|
||||||
|
## Loading images
|
||||||
|
|
||||||
|
Once you've added the `Spectre.Console.Json` NuGet package,
|
||||||
|
you can start rendering JSON to the console.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
using Spectre.Console.Json;
|
||||||
|
|
||||||
|
var json = new JsonText(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"hello": 32,
|
||||||
|
"world": {
|
||||||
|
"foo": 21,
|
||||||
|
"bar": 255,
|
||||||
|
"baz": [
|
||||||
|
0.32, 0.33e-32,
|
||||||
|
0.42e32, 0.55e+32,
|
||||||
|
{
|
||||||
|
"hello": "world",
|
||||||
|
"lol": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AnsiConsole.Write(
|
||||||
|
new Panel(json)
|
||||||
|
.Header("Some JSON in a panel")
|
||||||
|
.Collapse()
|
||||||
|
.RoundedBorder()
|
||||||
|
.BorderColor(Color.Yellow));
|
||||||
|
```
|
||||||
|
|
||||||
|
### Result
|
||||||
|
|
||||||
|
<?# AsciiCast cast="json" /?>
|
||||||
|
|
||||||
|
## Styling
|
||||||
|
|
||||||
|
All the different JSON parts can be customized to have unique styles.
|
||||||
|
|
||||||
|
```csharp
|
||||||
|
AnsiConsole.Write(
|
||||||
|
new JsonText(json)
|
||||||
|
.BracesColor(Color.Red)
|
||||||
|
.BracketColor(Color.Green)
|
||||||
|
.ColonColor(Color.Blue)
|
||||||
|
.CommaColor(Color.Red)
|
||||||
|
.StringColor(Color.Green)
|
||||||
|
.NumberColor(Color.Blue)
|
||||||
|
.BooleanColor(Color.Red)
|
||||||
|
.NullColor(Color.Green));
|
||||||
|
```
|
@ -15,7 +15,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"dotnet-example": {
|
"dotnet-example": {
|
||||||
"version": "1.6.0",
|
"version": "2.0.0",
|
||||||
"commands": [
|
"commands": [
|
||||||
"dotnet-example"
|
"dotnet-example"
|
||||||
]
|
]
|
||||||
|
16
examples/Console/Json/Json.csproj
Normal file
16
examples/Console/Json/Json.csproj
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<OutputType>Exe</OutputType>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<ExampleTitle>Json</ExampleTitle>
|
||||||
|
<ExampleDescription>Demonstrates how to print syntax highlighted JSON.</ExampleDescription>
|
||||||
|
<ExampleGroup>Widgets</ExampleGroup>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\Shared\Shared.csproj" />
|
||||||
|
<ProjectReference Include="..\..\..\src\Spectre.Console.Json\Spectre.Console.Json.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
36
examples/Console/Json/Program.cs
Normal file
36
examples/Console/Json/Program.cs
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
using Spectre.Console;
|
||||||
|
using Spectre.Console.Json;
|
||||||
|
|
||||||
|
namespace Json;
|
||||||
|
|
||||||
|
public static class Program
|
||||||
|
{
|
||||||
|
public static void Main()
|
||||||
|
{
|
||||||
|
var json = new JsonText(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"hello": 32,
|
||||||
|
"world": {
|
||||||
|
"foo": 21,
|
||||||
|
"bar": 255,
|
||||||
|
"baz": [
|
||||||
|
0.32, 0.33e-32,
|
||||||
|
0.42e32, 0.55e+32,
|
||||||
|
{
|
||||||
|
"hello": "world",
|
||||||
|
"lol": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AnsiConsole.Write(
|
||||||
|
new Panel(json)
|
||||||
|
.Header("Some JSON in a panel")
|
||||||
|
.Collapse()
|
||||||
|
.RoundedBorder()
|
||||||
|
.BorderColor(Color.Yellow));
|
||||||
|
}
|
||||||
|
}
|
@ -79,6 +79,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "..\s
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layout", "Console\Layout\Layout.csproj", "{A9FDE73A-8452-4CA3-B366-3F900597E132}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layout", "Console\Layout\Layout.csproj", "{A9FDE73A-8452-4CA3-B366-3F900597E132}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Json", "Console\Json\Json.csproj", "{ABE3E734-0756-4D5A-B28A-E6E526D9927D}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -521,6 +523,18 @@ Global
|
|||||||
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x64.Build.0 = Release|Any CPU
|
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.ActiveCfg = Release|Any CPU
|
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.Build.0 = Release|Any CPU
|
{A9FDE73A-8452-4CA3-B366-3F900597E132}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{ABE3E734-0756-4D5A-B28A-E6E526D9927D}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -8,9 +8,9 @@ namespace Generator.Commands.Samples
|
|||||||
|
|
||||||
public override void Run(IAnsiConsole console)
|
public override void Run(IAnsiConsole console)
|
||||||
{
|
{
|
||||||
console.Write(new FigletText("Left aligned").LeftAligned().Color(Color.Red));
|
console.Write(new FigletText("Left aligned").LeftJustified().Color(Color.Red));
|
||||||
console.Write(new FigletText("Centered").Centered().Color(Color.Green));
|
console.Write(new FigletText("Centered").Centered().Color(Color.Green));
|
||||||
console.Write(new FigletText("Right aligned").RightAligned().Color(Color.Blue));
|
console.Write(new FigletText("Right aligned").RightJustified().Color(Color.Blue));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -19,7 +19,7 @@ namespace Generator.Commands.Samples
|
|||||||
console.DisplayThenType(c => password = AskPassword(c), "hunter2↲");
|
console.DisplayThenType(c => password = AskPassword(c), "hunter2↲");
|
||||||
console.DisplayThenType(c => color = AskColor(c), "↲");
|
console.DisplayThenType(c => color = AskColor(c), "↲");
|
||||||
|
|
||||||
AnsiConsole.Write(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftAligned());
|
AnsiConsole.Write(new Rule("[yellow]Results[/]").RuleStyle("grey").LeftJustified());
|
||||||
AnsiConsole.Write(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
|
AnsiConsole.Write(new Table().AddColumns("[grey]Question[/]", "[grey]Answer[/]")
|
||||||
.RoundedBorder()
|
.RoundedBorder()
|
||||||
.BorderColor(Color.Grey)
|
.BorderColor(Color.Grey)
|
||||||
@ -33,7 +33,7 @@ namespace Generator.Commands.Samples
|
|||||||
private static string AskName(IAnsiConsole console)
|
private static string AskName(IAnsiConsole console)
|
||||||
{
|
{
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
console.Write(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftAligned());
|
console.Write(new Rule("[yellow]Strings[/]").RuleStyle("grey").LeftJustified());
|
||||||
var name = console.Ask<string>("What's your [green]name[/]?");
|
var name = console.Ask<string>("What's your [green]name[/]?");
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
@ -42,7 +42,7 @@ namespace Generator.Commands.Samples
|
|||||||
private static string AskSport(IAnsiConsole console)
|
private static string AskSport(IAnsiConsole console)
|
||||||
{
|
{
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
console.Write(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftAligned());
|
console.Write(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftJustified());
|
||||||
|
|
||||||
return console.Prompt(
|
return console.Prompt(
|
||||||
new TextPrompt<string>("What's your [green]favorite sport[/]?")
|
new TextPrompt<string>("What's your [green]favorite sport[/]?")
|
||||||
@ -56,7 +56,7 @@ namespace Generator.Commands.Samples
|
|||||||
private static int AskAge(IAnsiConsole console)
|
private static int AskAge(IAnsiConsole console)
|
||||||
{
|
{
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
console.Write(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftAligned());
|
console.Write(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftJustified());
|
||||||
|
|
||||||
return console.Prompt(
|
return console.Prompt(
|
||||||
new TextPrompt<int>("How [green]old[/] are you?")
|
new TextPrompt<int>("How [green]old[/] are you?")
|
||||||
@ -76,7 +76,7 @@ namespace Generator.Commands.Samples
|
|||||||
private static string AskPassword(IAnsiConsole console)
|
private static string AskPassword(IAnsiConsole console)
|
||||||
{
|
{
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
console.Write(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftAligned());
|
console.Write(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftJustified());
|
||||||
|
|
||||||
return console.Prompt(
|
return console.Prompt(
|
||||||
new TextPrompt<string>("Enter [green]password[/]?")
|
new TextPrompt<string>("Enter [green]password[/]?")
|
||||||
@ -87,7 +87,7 @@ namespace Generator.Commands.Samples
|
|||||||
private static string AskColor(IAnsiConsole console)
|
private static string AskColor(IAnsiConsole console)
|
||||||
{
|
{
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
console.Write(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftAligned());
|
console.Write(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftJustified());
|
||||||
|
|
||||||
return console.Prompt(
|
return console.Prompt(
|
||||||
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
|
new TextPrompt<string>("[grey][[Optional]][/] What is your [green]favorite color[/]?")
|
||||||
|
@ -0,0 +1,39 @@
|
|||||||
|
using Spectre.Console;
|
||||||
|
using Spectre.Console.Json;
|
||||||
|
|
||||||
|
namespace Generator.Commands.Samples
|
||||||
|
{
|
||||||
|
public class JsonSample : BaseSample
|
||||||
|
{
|
||||||
|
public override (int Cols, int Rows) ConsoleSize => (60, 20);
|
||||||
|
|
||||||
|
public override void Run(IAnsiConsole console)
|
||||||
|
{
|
||||||
|
var json = new JsonText(
|
||||||
|
"""
|
||||||
|
{
|
||||||
|
"hello": 32,
|
||||||
|
"world": {
|
||||||
|
"foo": 21,
|
||||||
|
"bar": 255,
|
||||||
|
"baz": [
|
||||||
|
0.32, 0.33e-32,
|
||||||
|
0.42e32, 0.55e+32,
|
||||||
|
{
|
||||||
|
"hello": "world",
|
||||||
|
"lol": null
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
""");
|
||||||
|
|
||||||
|
AnsiConsole.Write(
|
||||||
|
new Panel(json)
|
||||||
|
.Header("Some JSON in a panel")
|
||||||
|
.Collapse()
|
||||||
|
.RoundedBorder()
|
||||||
|
.BorderColor(Color.Yellow));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,11 +10,11 @@ namespace Generator.Commands.Samples
|
|||||||
{
|
{
|
||||||
console.Write(new Rule());
|
console.Write(new Rule());
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
console.Write(new Rule("[blue]Left aligned[/]").LeftAligned().RuleStyle("red"));
|
console.Write(new Rule("[blue]Left aligned[/]").LeftJustified().RuleStyle("red"));
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
console.Write(new Rule("[green]Centered[/]").Centered().RuleStyle("green"));
|
console.Write(new Rule("[green]Centered[/]").Centered().RuleStyle("green"));
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
console.Write(new Rule("[red]Right aligned[/]").RightAligned().RuleStyle("blue"));
|
console.Write(new Rule("[red]Right aligned[/]").RightJustified().RuleStyle("blue"));
|
||||||
console.WriteLine();
|
console.WriteLine();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,9 @@ namespace Generator.Commands.Samples
|
|||||||
console.Write(
|
console.Write(
|
||||||
new Panel(
|
new Panel(
|
||||||
new Padder(new Rows(
|
new Padder(new Rows(
|
||||||
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").LeftAligned(),
|
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").LeftJustified(),
|
||||||
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").Centered(),
|
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").Centered(),
|
||||||
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").RightAligned()), new Padding(0,1)))
|
new TextPath("/This/Is/A/Long/Path/That/Will/Be/Truncated.txt").RightJustified()), new Padding(0,1)))
|
||||||
.BorderStyle(new Style(foreground: Color.Grey))
|
.BorderStyle(new Style(foreground: Color.Grey))
|
||||||
.Header("Alignment"));
|
.Header("Alignment"));
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<OutputType>Exe</OutputType>
|
<OutputType>Exe</OutputType>
|
||||||
@ -51,8 +51,9 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj" />
|
<ProjectReference Include="..\..\..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj" />
|
<ProjectReference Include="..\..\..\src\Spectre.Console.ImageSharp\Spectre.Console.ImageSharp.csproj" />
|
||||||
<ProjectReference Include="..\..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
<ProjectReference Include="..\..\..\src\Spectre.Console.Json\Spectre.Console.Json.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
14
src/Spectre.Console.Json/IJsonParser.cs
Normal file
14
src/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/Spectre.Console.Json/JsonBuilder.cs
Normal file
101
src/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/Spectre.Console.Json/JsonParser.cs
Normal file
146
src/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/Spectre.Console.Json/JsonText.cs
Normal file
106
src/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 ?? new Style(Color.Grey),
|
||||||
|
BracketsStyle = BracketsStyle ?? new Style(Color.Grey),
|
||||||
|
MemberStyle = MemberStyle ?? new Style(Color.Blue),
|
||||||
|
ColonStyle = ColonStyle ?? new Style(Color.Yellow),
|
||||||
|
CommaStyle = CommaStyle ?? new Style(Color.Grey),
|
||||||
|
StringStyle = StringStyle ?? new Style(Color.Red),
|
||||||
|
NumberStyle = NumberStyle ?? new Style(Color.Green),
|
||||||
|
BooleanStyle = BooleanStyle ?? new Style(Color.Green),
|
||||||
|
NullStyle = NullStyle ?? new Style(Color.Grey),
|
||||||
|
});
|
||||||
|
|
||||||
|
_syntax.Accept(JsonBuilder.Shared, context);
|
||||||
|
return context.Paragraph;
|
||||||
|
}
|
||||||
|
}
|
313
src/Spectre.Console.Json/JsonTextExtensions.cs
Normal file
313
src/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/Spectre.Console.Json/JsonTextStyles.cs
Normal file
14
src/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/Spectre.Console.Json/JsonToken.cs
Normal file
13
src/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/Spectre.Console.Json/JsonTokenReader.cs
Normal file
55
src/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/Spectre.Console.Json/JsonTokenType.cs
Normal file
15
src/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/Spectre.Console.Json/JsonTokenizer.cs
Normal file
205
src/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/Spectre.Console.Json/Properties/Usings.cs
Normal file
4
src/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;
|
26
src/Spectre.Console.Json/Spectre.Console.Json.csproj
Normal file
26
src/Spectre.Console.Json/Spectre.Console.Json.csproj
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFrameworks>net7.0;net6.0;netstandard2.0</TargetFrameworks>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<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>
|
||||||
|
<AdditionalFiles Include="..\stylecop.json" Link="Properties/stylecop.json" />
|
||||||
|
<None Include="../../resources/gfx/small-logo.png" Pack="true" PackagePath="\" Link="Properties/small-logo.png" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Spectre.Console\Spectre.Console.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
25
src/Spectre.Console.Json/Syntax/JsonArray.cs
Normal file
25
src/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/Spectre.Console.Json/Syntax/JsonBoolean.cs
Normal file
26
src/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/Spectre.Console.Json/Syntax/JsonMember.cs
Normal file
33
src/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/Spectre.Console.Json/Syntax/JsonNull.cs
Normal file
26
src/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/Spectre.Console.Json/Syntax/JsonNumber.cs
Normal file
16
src/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/Spectre.Console.Json/Syntax/JsonObject.cs
Normal file
25
src/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/Spectre.Console.Json/Syntax/JsonString.cs
Normal file
26
src/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/Spectre.Console.Json/Syntax/JsonSyntax.cs
Normal file
9
src/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);
|
||||||
|
}
|
12
src/Spectre.Console.Json/Syntax/JsonSyntaxVisitor.cs
Normal file
12
src/Spectre.Console.Json/Syntax/JsonSyntaxVisitor.cs
Normal file
@ -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);
|
||||||
|
}
|
@ -34,6 +34,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli", "Spec
|
|||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli.Tests", "..\test\Spectre.Console.Cli.Tests\Spectre.Console.Cli.Tests.csproj", "{E07C46D2-714F-4116-BADD-FEE09617A9C4}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Cli.Tests", "..\test\Spectre.Console.Cli.Tests\Spectre.Console.Cli.Tests.csproj", "{E07C46D2-714F-4116-BADD-FEE09617A9C4}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Json", "Spectre.Console.Json\Spectre.Console.Json.csproj", "{579E6E31-1E2F-4FE1-8F8C-9770878993E9}"
|
||||||
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
Debug|Any CPU = Debug|Any CPU
|
Debug|Any CPU = Debug|Any CPU
|
||||||
@ -116,6 +118,18 @@ Global
|
|||||||
{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x64.Build.0 = Release|Any CPU
|
{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x64.Build.0 = Release|Any CPU
|
||||||
{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x86.ActiveCfg = Release|Any CPU
|
{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x86.Build.0 = Release|Any CPU
|
{E07C46D2-714F-4116-BADD-FEE09617A9C4}.Release|x86.Build.0 = Release|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|x64.ActiveCfg = Debug|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|x64.Build.0 = Debug|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|x86.ActiveCfg = Debug|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Debug|x86.Build.0 = Debug|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|x64.ActiveCfg = Release|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|x64.Build.0 = Release|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|x86.ActiveCfg = Release|Any CPU
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9}.Release|x86.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
@ -123,6 +137,7 @@ Global
|
|||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F}
|
{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F}
|
||||||
{0EFE694D-0770-4E71-BF4E-EC2B41362F79} = {E0E45070-123C-4A4D-AA98-2A780308876C}
|
{0EFE694D-0770-4E71-BF4E-EC2B41362F79} = {E0E45070-123C-4A4D-AA98-2A780308876C}
|
||||||
|
{579E6E31-1E2F-4FE1-8F8C-9770878993E9} = {E0E45070-123C-4A4D-AA98-2A780308876C}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C}
|
||||||
|
@ -3,7 +3,7 @@ namespace Spectre.Console;
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Contains extension methods for <see cref="char"/>.
|
/// Contains extension methods for <see cref="char"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public static class CharExtensions
|
public static partial class CharExtensions
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the cell width of a character.
|
/// Gets the cell width of a character.
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
namespace Spectre.Console.Internal;
|
||||||
|
|
||||||
|
internal static partial class CharExtensions
|
||||||
|
{
|
||||||
|
public static bool IsDigit(this char character, int min = 0)
|
||||||
|
{
|
||||||
|
return char.IsDigit(character) && character >= (char)min;
|
||||||
|
}
|
||||||
|
}
|
@ -118,7 +118,7 @@ internal static class EnumerableExtensions
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> Zip<TFirst, TSecond, TThird>(
|
public static IEnumerable<(TFirst First, TSecond Second, TThird Third)> ZipThree<TFirst, TSecond, TThird>(
|
||||||
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
|
this IEnumerable<TFirst> first, IEnumerable<TSecond> second, IEnumerable<TThird> third)
|
||||||
{
|
{
|
||||||
return first.Zip(second, (a, b) => (a, b))
|
return first.Zip(second, (a, b) => (a, b))
|
@ -89,7 +89,7 @@ internal static class Ratio
|
|||||||
var totalRemaining = total;
|
var totalRemaining = total;
|
||||||
var result = new List<int>();
|
var result = new List<int>();
|
||||||
|
|
||||||
foreach (var (ratio, maximum, value) in ratios.Zip(maximums, values))
|
foreach (var (ratio, maximum, value) in ratios.ZipThree(maximums, values))
|
||||||
{
|
{
|
||||||
if (ratio != 0 && totalRatio > 0)
|
if (ratio != 0 && totalRatio > 0)
|
||||||
{
|
{
|
||||||
|
@ -23,11 +23,22 @@ internal sealed class StringBuffer : IDisposable
|
|||||||
_reader.Dispose();
|
_reader.Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public char Expect(char character)
|
||||||
|
{
|
||||||
|
var read = Read();
|
||||||
|
if (read != character)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException($"Expected '{character}', but found '{read}'");
|
||||||
|
}
|
||||||
|
|
||||||
|
return read;
|
||||||
|
}
|
||||||
|
|
||||||
public char Peek()
|
public char Peek()
|
||||||
{
|
{
|
||||||
if (Eof)
|
if (Eof)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Tried to peek past the end of the text.");
|
return '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
return (char)_reader.Peek();
|
return (char)_reader.Peek();
|
||||||
@ -37,7 +48,7 @@ internal sealed class StringBuffer : IDisposable
|
|||||||
{
|
{
|
||||||
if (Eof)
|
if (Eof)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException("Tried to read past the end of the text.");
|
return '\0';
|
||||||
}
|
}
|
||||||
|
|
||||||
Position++;
|
Position++;
|
||||||
|
@ -36,6 +36,10 @@
|
|||||||
</PackageReference>
|
</PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Widgets\Json\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<DefineConstants>$(DefineConstants)TRACE;WCWIDTH_VISIBILITY_INTERNAL</DefineConstants>
|
<DefineConstants>$(DefineConstants)TRACE;WCWIDTH_VISIBILITY_INTERNAL</DefineConstants>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
68
test/Spectre.Console.Tests/Data/example.json
Normal file
68
test/Spectre.Console.Tests/Data/example.json
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"id": "0001",
|
||||||
|
"type": "donut",
|
||||||
|
"name": "Cake",
|
||||||
|
"ppu": 0.55,
|
||||||
|
"foo": true,
|
||||||
|
"bar": false,
|
||||||
|
"qux": 32,
|
||||||
|
"corgi": null,
|
||||||
|
"batters": {
|
||||||
|
"batter": [
|
||||||
|
{
|
||||||
|
"id": "1001",
|
||||||
|
"type": "Regular",
|
||||||
|
"min": 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1002",
|
||||||
|
"type": "Chocolate",
|
||||||
|
"min": 0.32
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1003",
|
||||||
|
"min": 12.32,
|
||||||
|
"type": "Blueberry"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "1004",
|
||||||
|
"min": 0.32E-12,
|
||||||
|
"type": "Devil's Food"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"topping": [
|
||||||
|
{
|
||||||
|
"id": "5001",
|
||||||
|
"min": 0.32e-12,
|
||||||
|
"type": "None"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5002",
|
||||||
|
"min": 0.32E+12,
|
||||||
|
"type": "Glazed"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5005",
|
||||||
|
"min": 0.32e+12,
|
||||||
|
"type": "Sugar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5007",
|
||||||
|
"min": 0.32e12,
|
||||||
|
"type": "Powdered Sugar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5006",
|
||||||
|
"type": "Chocolate with Sprinkles"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5003",
|
||||||
|
"type": "Chocolate"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5004",
|
||||||
|
"type": "Maple"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,70 @@
|
|||||||
|
┌─Some JSON───────────────────────────────────┐
|
||||||
|
│ { │
|
||||||
|
│ "id": "0001", │
|
||||||
|
│ "type": "donut", │
|
||||||
|
│ "name": "Cake", │
|
||||||
|
│ "ppu": 0.55, │
|
||||||
|
│ "foo": true, │
|
||||||
|
│ "bar": false, │
|
||||||
|
│ "qux": 32, │
|
||||||
|
│ "corgi": null, │
|
||||||
|
│ "batters": { │
|
||||||
|
│ "batter": [ │
|
||||||
|
│ { │
|
||||||
|
│ "id": "1001", │
|
||||||
|
│ "type": "Regular", │
|
||||||
|
│ "min": 0 │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "1002", │
|
||||||
|
│ "type": "Chocolate", │
|
||||||
|
│ "min": 0.32 │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "1003", │
|
||||||
|
│ "min": 12.32, │
|
||||||
|
│ "type": "Blueberry" │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "1004", │
|
||||||
|
│ "min": 0.32E-12, │
|
||||||
|
│ "type": "Devil's Food" │
|
||||||
|
│ } │
|
||||||
|
│ ] │
|
||||||
|
│ }, │
|
||||||
|
│ "topping": [ │
|
||||||
|
│ { │
|
||||||
|
│ "id": "5001", │
|
||||||
|
│ "min": 0.32e-12, │
|
||||||
|
│ "type": "None" │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "5002", │
|
||||||
|
│ "min": 0.32E+12, │
|
||||||
|
│ "type": "Glazed" │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "5005", │
|
||||||
|
│ "min": 0.32e+12, │
|
||||||
|
│ "type": "Sugar" │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "5007", │
|
||||||
|
│ "min": 0.32e12, │
|
||||||
|
│ "type": "Powdered Sugar" │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "5006", │
|
||||||
|
│ "type": "Chocolate with Sprinkles" │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "5003", │
|
||||||
|
│ "type": "Chocolate" │
|
||||||
|
│ }, │
|
||||||
|
│ { │
|
||||||
|
│ "id": "5004", │
|
||||||
|
│ "type": "Maple" │
|
||||||
|
│ } │
|
||||||
|
│ ] │
|
||||||
|
│ } │
|
||||||
|
└─────────────────────────────────────────────┘
|
17
test/Spectre.Console.Tests/Extensions/StreamExtensions.cs
Normal file
17
test/Spectre.Console.Tests/Extensions/StreamExtensions.cs
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
namespace Spectre.Console.Tests;
|
||||||
|
|
||||||
|
public static class StreamExtensions
|
||||||
|
{
|
||||||
|
public static string ReadText(this Stream stream)
|
||||||
|
{
|
||||||
|
if (stream is null)
|
||||||
|
{
|
||||||
|
throw new ArgumentNullException(nameof(stream));
|
||||||
|
}
|
||||||
|
|
||||||
|
using (var reader = new StreamReader(stream))
|
||||||
|
{
|
||||||
|
return reader.ReadToEnd();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -10,6 +10,7 @@ global using System.Threading;
|
|||||||
global using System.Threading.Tasks;
|
global using System.Threading.Tasks;
|
||||||
global using Shouldly;
|
global using Shouldly;
|
||||||
global using Spectre.Console.Advanced;
|
global using Spectre.Console.Advanced;
|
||||||
|
global using Spectre.Console.Json;
|
||||||
global using Spectre.Console.Rendering;
|
global using Spectre.Console.Rendering;
|
||||||
global using Spectre.Console.Testing;
|
global using Spectre.Console.Testing;
|
||||||
global using Spectre.Console.Tests.Data;
|
global using Spectre.Console.Tests.Data;
|
||||||
|
@ -6,7 +6,9 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<AdditionalFiles Include="..\..\src\stylecop.json" Link="Properties/stylecop.json" />
|
<AdditionalFiles Include="..\..\src\stylecop.json" Link="Properties/stylecop.json" />
|
||||||
|
<None Remove="Data\example.json" />
|
||||||
<None Remove="Data\starwars.flf" />
|
<None Remove="Data\starwars.flf" />
|
||||||
|
<EmbeddedResource Include="Data\example.json" />
|
||||||
<EmbeddedResource Include="Data\starwars.flf" />
|
<EmbeddedResource Include="Data\starwars.flf" />
|
||||||
<None Remove="Data\poison.flf" />
|
<None Remove="Data\poison.flf" />
|
||||||
<EmbeddedResource Include="Data\poison.flf" />
|
<EmbeddedResource Include="Data\poison.flf" />
|
||||||
@ -29,6 +31,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj" />
|
<ProjectReference Include="..\..\src\Spectre.Console.Cli\Spectre.Console.Cli.csproj" />
|
||||||
|
<ProjectReference Include="..\..\src\Spectre.Console.Json\Spectre.Console.Json.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Spectre.Console.Testing\Spectre.Console.Testing.csproj" />
|
<ProjectReference Include="..\..\src\Spectre.Console.Testing\Spectre.Console.Testing.csproj" />
|
||||||
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
<ProjectReference Include="..\..\src\Spectre.Console\Spectre.Console.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
23
test/Spectre.Console.Tests/Unit/Widgets/JsonTextTests.cs
Normal file
23
test/Spectre.Console.Tests/Unit/Widgets/JsonTextTests.cs
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
namespace Spectre.Console.Tests.Unit;
|
||||||
|
|
||||||
|
[UsesVerify]
|
||||||
|
[ExpectationPath("Widgets/Json")]
|
||||||
|
public sealed class JsonTextTests
|
||||||
|
{
|
||||||
|
[Fact]
|
||||||
|
[Expectation("Render_Json")]
|
||||||
|
public Task Should_Render_Json()
|
||||||
|
{
|
||||||
|
// Given
|
||||||
|
var console = new TestConsole().Size(new Size(80, 15));
|
||||||
|
var json = EmbeddedResourceReader
|
||||||
|
.LoadResourceStream("Spectre.Console.Tests/Data/example.json")
|
||||||
|
.ReadText();
|
||||||
|
|
||||||
|
// When
|
||||||
|
console.Write(new Panel(new JsonText(json)).Header("Some JSON"));
|
||||||
|
|
||||||
|
// Then
|
||||||
|
return Verifier.Verify(console.Output);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user