mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 09:09:25 +08:00 
			
		
		
		
	Add JSON text renderer (#1086)
* Add JsonText widget to render highlighted JSON Closes #1051
This commit is contained in:
		| @@ -23,7 +23,8 @@ namespace Docs | ||||
|                 { | ||||
|                     "../../src/Spectre.Console/**/{!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> | ||||
|                     { | ||||
|   | ||||
							
								
								
									
										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": { | ||||
|       "version": "1.6.0", | ||||
|       "version": "2.0.0", | ||||
|       "commands": [ | ||||
|         "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 | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Layout", "Console\Layout\Layout.csproj", "{A9FDE73A-8452-4CA3-B366-3F900597E132}" | ||||
| EndProject | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Json", "Console\Json\Json.csproj", "{ABE3E734-0756-4D5A-B28A-E6E526D9927D}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		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|x86.ActiveCfg = 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 | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
|   | ||||
| @@ -8,9 +8,9 @@ namespace Generator.Commands.Samples | ||||
|  | ||||
|         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("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 => 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[/]") | ||||
|                 .RoundedBorder() | ||||
|                 .BorderColor(Color.Grey) | ||||
| @@ -33,7 +33,7 @@ namespace Generator.Commands.Samples | ||||
|         private static string AskName(IAnsiConsole console) | ||||
|         { | ||||
|             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[/]?"); | ||||
|             return name; | ||||
|         } | ||||
| @@ -42,7 +42,7 @@ namespace Generator.Commands.Samples | ||||
|         private static string AskSport(IAnsiConsole console) | ||||
|         { | ||||
|             console.WriteLine(); | ||||
|             console.Write(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftAligned()); | ||||
|             console.Write(new Rule("[yellow]Choices[/]").RuleStyle("grey").LeftJustified()); | ||||
|  | ||||
|             return console.Prompt( | ||||
|                 new TextPrompt<string>("What's your [green]favorite sport[/]?") | ||||
| @@ -56,7 +56,7 @@ namespace Generator.Commands.Samples | ||||
|         private static int AskAge(IAnsiConsole console) | ||||
|         { | ||||
|             console.WriteLine(); | ||||
|             console.Write(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftAligned()); | ||||
|             console.Write(new Rule("[yellow]Integers[/]").RuleStyle("grey").LeftJustified()); | ||||
|  | ||||
|             return console.Prompt( | ||||
|                 new TextPrompt<int>("How [green]old[/] are you?") | ||||
| @@ -76,7 +76,7 @@ namespace Generator.Commands.Samples | ||||
|         private static string AskPassword(IAnsiConsole console) | ||||
|         { | ||||
|             console.WriteLine(); | ||||
|             console.Write(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftAligned()); | ||||
|             console.Write(new Rule("[yellow]Secrets[/]").RuleStyle("grey").LeftJustified()); | ||||
|  | ||||
|             return console.Prompt( | ||||
|                 new TextPrompt<string>("Enter [green]password[/]?") | ||||
| @@ -87,7 +87,7 @@ namespace Generator.Commands.Samples | ||||
|         private static string AskColor(IAnsiConsole console) | ||||
|         { | ||||
|             console.WriteLine(); | ||||
|             console.Write(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftAligned()); | ||||
|             console.Write(new Rule("[yellow]Optional[/]").RuleStyle("grey").LeftJustified()); | ||||
|  | ||||
|             return console.Prompt( | ||||
|                 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.WriteLine(); | ||||
|             console.Write(new Rule("[blue]Left aligned[/]").LeftAligned().RuleStyle("red")); | ||||
|             console.Write(new Rule("[blue]Left aligned[/]").LeftJustified().RuleStyle("red")); | ||||
|             console.WriteLine(); | ||||
|             console.Write(new Rule("[green]Centered[/]").Centered().RuleStyle("green")); | ||||
|             console.WriteLine(); | ||||
|             console.Write(new Rule("[red]Right aligned[/]").RightAligned().RuleStyle("blue")); | ||||
|             console.Write(new Rule("[red]Right aligned[/]").RightJustified().RuleStyle("blue")); | ||||
|             console.WriteLine(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
| @@ -34,9 +34,9 @@ namespace Generator.Commands.Samples | ||||
|             console.Write( | ||||
|                 new Panel( | ||||
|                     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").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)) | ||||
|                 .Header("Alignment")); | ||||
|         } | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
| <Project Sdk="Microsoft.NET.Sdk"> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <OutputType>Exe</OutputType> | ||||
| @@ -51,8 +51,9 @@ | ||||
|   </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.ImageSharp\Spectre.Console.ImageSharp.csproj" /> | ||||
|     <ProjectReference Include="..\..\..\src\Spectre.Console\Spectre.Console.csproj" /> | ||||
|     <ProjectReference Include="..\..\..\src\Spectre.Console.Json\Spectre.Console.Json.csproj" /> | ||||
|   </ItemGroup> | ||||
| </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 | ||||
| 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 | ||||
| Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Spectre.Console.Json", "Spectre.Console.Json\Spectre.Console.Json.csproj", "{579E6E31-1E2F-4FE1-8F8C-9770878993E9}" | ||||
| EndProject | ||||
| Global | ||||
| 	GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||||
| 		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|x86.ActiveCfg = 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 | ||||
| 	GlobalSection(SolutionProperties) = preSolution | ||||
| 		HideSolutionNode = FALSE | ||||
| @@ -123,6 +137,7 @@ Global | ||||
| 	GlobalSection(NestedProjects) = preSolution | ||||
| 		{C3E2CB5C-1517-4C75-B59A-93D4E22BEC8D} = {20595AD4-8D75-4AF8-B6BC-9C38C160423F} | ||||
| 		{0EFE694D-0770-4E71-BF4E-EC2B41362F79} = {E0E45070-123C-4A4D-AA98-2A780308876C} | ||||
| 		{579E6E31-1E2F-4FE1-8F8C-9770878993E9} = {E0E45070-123C-4A4D-AA98-2A780308876C} | ||||
| 	EndGlobalSection | ||||
| 	GlobalSection(ExtensibilityGlobals) = postSolution | ||||
| 		SolutionGuid = {5729B071-67A0-48FB-8B1B-275E6822086C} | ||||
|   | ||||
| @@ -3,7 +3,7 @@ namespace Spectre.Console; | ||||
| /// <summary> | ||||
| /// Contains extension methods for <see cref="char"/>. | ||||
| /// </summary> | ||||
| public static class CharExtensions | ||||
| public static partial class CharExtensions | ||||
| { | ||||
|     /// <summary> | ||||
|     /// 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 | ||||
| 
 | ||||
|     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) | ||||
|     { | ||||
|         return first.Zip(second, (a, b) => (a, b)) | ||||
| @@ -89,7 +89,7 @@ internal static class Ratio | ||||
|         var totalRemaining = total; | ||||
|         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) | ||||
|             { | ||||
|   | ||||
| @@ -23,11 +23,22 @@ internal sealed class StringBuffer : IDisposable | ||||
|         _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() | ||||
|     { | ||||
|         if (Eof) | ||||
|         { | ||||
|             throw new InvalidOperationException("Tried to peek past the end of the text."); | ||||
|             return '\0'; | ||||
|         } | ||||
|  | ||||
|         return (char)_reader.Peek(); | ||||
| @@ -37,7 +48,7 @@ internal sealed class StringBuffer : IDisposable | ||||
|     { | ||||
|         if (Eof) | ||||
|         { | ||||
|             throw new InvalidOperationException("Tried to read past the end of the text."); | ||||
|             return '\0'; | ||||
|         } | ||||
|  | ||||
|         Position++; | ||||
|   | ||||
| @@ -36,6 +36,10 @@ | ||||
|     </PackageReference> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <Folder Include="Widgets\Json\" /> | ||||
|   </ItemGroup> | ||||
|  | ||||
|   <PropertyGroup> | ||||
|     <DefineConstants>$(DefineConstants)TRACE;WCWIDTH_VISIBILITY_INTERNAL</DefineConstants> | ||||
|   </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 Shouldly; | ||||
| global using Spectre.Console.Advanced; | ||||
| global using Spectre.Console.Json; | ||||
| global using Spectre.Console.Rendering; | ||||
| global using Spectre.Console.Testing; | ||||
| global using Spectre.Console.Tests.Data; | ||||
|   | ||||
| @@ -6,7 +6,9 @@ | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <AdditionalFiles Include="..\..\src\stylecop.json" Link="Properties/stylecop.json" /> | ||||
|     <None Remove="Data\example.json" /> | ||||
|     <None Remove="Data\starwars.flf" /> | ||||
|     <EmbeddedResource Include="Data\example.json" /> | ||||
|     <EmbeddedResource Include="Data\starwars.flf" /> | ||||
|     <None Remove="Data\poison.flf" /> | ||||
|     <EmbeddedResource Include="Data\poison.flf" /> | ||||
| @@ -29,6 +31,7 @@ | ||||
|  | ||||
|   <ItemGroup> | ||||
|     <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\Spectre.Console.csproj" /> | ||||
|   </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); | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Patrik Svensson
					Patrik Svensson