mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 09:09:25 +08:00 
			
		
		
		
	Add parser and renderer for markup language
This commit is contained in:
		 Patrik Svensson
					Patrik Svensson
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						parent
						
							b72a695c35
						
					
				
				
					commit
					0986a5f744
				
			
							
								
								
									
										289
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										289
									
								
								README.md
									
									
									
									
									
								
							| @@ -6,6 +6,16 @@ A .NET Standard 2.0 library that makes it easier to create beautiful console app | ||||
| It is heavily inspired by the excellent [Rich library](https://github.com/willmcgugan/rich)  | ||||
| for Python. | ||||
|  | ||||
| ## Table of Contents | ||||
|  | ||||
| 1. [Features](#features) | ||||
| 2. [Example](#example) | ||||
| 3. [Usage](#usage)   | ||||
|    3.1. [Using the static API](#using-the-static-api)   | ||||
|    3.2. [Creating a console](#creating-a-console) | ||||
| 4. [Available styles](#available-styles) | ||||
| 5. [Predefiend colors](#predefined-colors) | ||||
|  | ||||
| ## Features | ||||
|  | ||||
| * Written with unit testing in mind. | ||||
| @@ -44,7 +54,7 @@ AnsiConsole.Style = Styles.Underline | Styles.Bold; | ||||
| AnsiConsole.WriteLine("Hello World!"); | ||||
|  | ||||
| AnsiConsole.Reset(); | ||||
| AnsiConsole.WriteLine("Good bye!"); | ||||
| AnsiConsole.MarkupLine("[yellow]{0}[/] [underline]world[/]!", "Goodbye"); | ||||
| ``` | ||||
|  | ||||
| If you want to get a reference to the default `IAnsiConsole`,  | ||||
| @@ -70,3 +80,280 @@ _NOTE: Even if you can specify a specific color system to use | ||||
| when manually creating a console, remember that the user's terminal  | ||||
| might not be able to use it, so unless you're creating an IAnsiConsole  | ||||
| for testing, always use `ColorSystemSupport.Detect` and `AnsiSupport.Detect`._ | ||||
|  | ||||
| ## Available styles | ||||
|  | ||||
| _NOTE: Not all styles are supported in every terminal._ | ||||
|  | ||||
| Name | Description | ||||
| --- | --- | ||||
| `bold` | Bold text | ||||
| `dim` | Dim or faint text | ||||
| `italic` | Italic text | ||||
| `underline` | Underlined text | ||||
| `invert` | Swaps the foreground and background colors | ||||
| `conceal` | Hides the text | ||||
| `slowblink` | Makes text blink slowly | ||||
| `rapidblink` | Makes text blink | ||||
| `strikethrough` | Shows text with a horizontal line through the center | ||||
|  | ||||
| ## Predefined colors | ||||
|  | ||||
| Number | Name | RGB | Hex | ||||
| --- | --- | --- | --- | ||||
| `0` | `black` | `0,0,0` | `#000000` | ||||
| `1` | `maroon` | `128,0,0` | `#800000` | ||||
| `2` | `green` | `0,128,0` | `#008000` | ||||
| `3` | `olive` | `128,128,0` | `#808000` | ||||
| `4` | `navy` | `0,0,128` | `#000080` | ||||
| `5` | `purple` | `128,0,128` | `#800080` | ||||
| `6` | `teal` | `0,128,128` | `#008080` | ||||
| `7` | `silver` | `192,192,192` | `#c0c0c0` | ||||
| `8` | `grey` | `128,128,128` | `#808080` | ||||
| `9` | `red` | `255,0,0` | `#ff0000` | ||||
| `10` | `lime` | `0,255,0` | `#00ff00` | ||||
| `11` | `yellow` | `255,255,0` | `#ffff00` | ||||
| `12` | `blue` | `0,0,255` | `#0000ff` | ||||
| `13` | `fuchsia` | `255,0,255` | `#ff00ff` | ||||
| `14` | `aqua` | `0,255,255` | `#00ffff` | ||||
| `15` | `white` | `255,255,255` | `#ffffff` | ||||
| `16` | `grey0` | `0,0,0` | `#000000` | ||||
| `17` | `navyblue` | `0,0,95` | `#00005f` | ||||
| `18` | `darkblue` | `0,0,135` | `#000087` | ||||
| `19` | `blue3` | `0,0,175` | `#0000af` | ||||
| `20` | `blue3_1` | `0,0,215` | `#0000d7` | ||||
| `21` | `blue1` | `0,0,255` | `#0000ff` | ||||
| `22` | `darkgreen` | `0,95,0` | `#005f00` | ||||
| `23` | `deepskyblue4` | `0,95,95` | `#005f5f` | ||||
| `24` | `deepskyblue4_1` | `0,95,135` | `#005f87` | ||||
| `25` | `deepskyblue4_2` | `0,95,175` | `#005faf` | ||||
| `26` | `dodgerblue3` | `0,95,215` | `#005fd7` | ||||
| `27` | `dodgerblue2` | `0,95,255` | `#005fff` | ||||
| `28` | `green4` | `0,135,0` | `#008700` | ||||
| `29` | `springgreen4` | `0,135,95` | `#00875f` | ||||
| `30` | `turquoise4` | `0,135,135` | `#008787` | ||||
| `31` | `deepskyblue3` | `0,135,175` | `#0087af` | ||||
| `32` | `deepskyblue3_1` | `0,135,215` | `#0087d7` | ||||
| `33` | `dodgerblue1` | `0,135,255` | `#0087ff` | ||||
| `34` | `green3` | `0,175,0` | `#00af00` | ||||
| `35` | `springgreen3` | `0,175,95` | `#00af5f` | ||||
| `36` | `darkcyan` | `0,175,135` | `#00af87` | ||||
| `37` | `lightseagreen` | `0,175,175` | `#00afaf` | ||||
| `38` | `deepskyblue2` | `0,175,215` | `#00afd7` | ||||
| `39` | `deepskyblue1` | `0,175,255` | `#00afff` | ||||
| `40` | `green3_1` | `0,215,0` | `#00d700` | ||||
| `41` | `springgreen3_1` | `0,215,95` | `#00d75f` | ||||
| `42` | `springgreen2` | `0,215,135` | `#00d787` | ||||
| `43` | `cyan3` | `0,215,175` | `#00d7af` | ||||
| `44` | `darkturquoise` | `0,215,215` | `#00d7d7` | ||||
| `45` | `turquoise2` | `0,215,255` | `#00d7ff` | ||||
| `46` | `green1` | `0,255,0` | `#00ff00` | ||||
| `47` | `springgreen2_1` | `0,255,95` | `#00ff5f` | ||||
| `48` | `springgreen1` | `0,255,135` | `#00ff87` | ||||
| `49` | `mediumspringgreen` | `0,255,175` | `#00ffaf` | ||||
| `50` | `cyan2` | `0,255,215` | `#00ffd7` | ||||
| `51` | `cyan1` | `0,255,255` | `#00ffff` | ||||
| `52` | `darkred` | `95,0,0` | `#5f0000` | ||||
| `53` | `deeppink4` | `95,0,95` | `#5f005f` | ||||
| `54` | `purple4` | `95,0,135` | `#5f0087` | ||||
| `55` | `purple4_1` | `95,0,175` | `#5f00af` | ||||
| `56` | `purple3` | `95,0,215` | `#5f00d7` | ||||
| `57` | `blueviolet` | `95,0,255` | `#5f00ff` | ||||
| `58` | `orange4` | `95,95,0` | `#5f5f00` | ||||
| `59` | `grey37` | `95,95,95` | `#5f5f5f` | ||||
| `60` | `mediumpurple4` | `95,95,135` | `#5f5f87` | ||||
| `61` | `slateblue3` | `95,95,175` | `#5f5faf` | ||||
| `62` | `slateblue3_1` | `95,95,215` | `#5f5fd7` | ||||
| `63` | `royalblue1` | `95,95,255` | `#5f5fff` | ||||
| `64` | `chartreuse4` | `95,135,0` | `#5f8700` | ||||
| `65` | `darkseagreen4` | `95,135,95` | `#5f875f` | ||||
| `66` | `paleturquoise4` | `95,135,135` | `#5f8787` | ||||
| `67` | `steelblue` | `95,135,175` | `#5f87af` | ||||
| `68` | `steelblue3` | `95,135,215` | `#5f87d7` | ||||
| `69` | `cornflowerblue` | `95,135,255` | `#5f87ff` | ||||
| `70` | `chartreuse3` | `95,175,0` | `#5faf00` | ||||
| `71` | `darkseagreen4_1` | `95,175,95` | `#5faf5f` | ||||
| `72` | `cadetblue` | `95,175,135` | `#5faf87` | ||||
| `73` | `cadetblue_1` | `95,175,175` | `#5fafaf` | ||||
| `74` | `skyblue3` | `95,175,215` | `#5fafd7` | ||||
| `75` | `steelblue1` | `95,175,255` | `#5fafff` | ||||
| `76` | `chartreuse3_1` | `95,215,0` | `#5fd700` | ||||
| `77` | `palegreen3` | `95,215,95` | `#5fd75f` | ||||
| `78` | `seagreen3` | `95,215,135` | `#5fd787` | ||||
| `79` | `aquamarine3` | `95,215,175` | `#5fd7af` | ||||
| `80` | `mediumturquoise` | `95,215,215` | `#5fd7d7` | ||||
| `81` | `steelblue1_1` | `95,215,255` | `#5fd7ff` | ||||
| `82` | `chartreuse2` | `95,255,0` | `#5fff00` | ||||
| `83` | `seagreen2` | `95,255,95` | `#5fff5f` | ||||
| `84` | `seagreen1` | `95,255,135` | `#5fff87` | ||||
| `85` | `seagreen1_1` | `95,255,175` | `#5fffaf` | ||||
| `86` | `aquamarine1` | `95,255,215` | `#5fffd7` | ||||
| `87` | `darkslategray2` | `95,255,255` | `#5fffff` | ||||
| `88` | `darkred_1` | `135,0,0` | `#870000` | ||||
| `89` | `deeppink4_1` | `135,0,95` | `#87005f` | ||||
| `90` | `darkmagenta` | `135,0,135` | `#870087` | ||||
| `91` | `darkmagenta_1` | `135,0,175` | `#8700af` | ||||
| `92` | `darkviolet` | `135,0,215` | `#8700d7` | ||||
| `93` | `purple_1` | `135,0,255` | `#8700ff` | ||||
| `94` | `orange4_1` | `135,95,0` | `#875f00` | ||||
| `95` | `lightpink4` | `135,95,95` | `#875f5f` | ||||
| `96` | `plum4` | `135,95,135` | `#875f87` | ||||
| `97` | `mediumpurple3` | `135,95,175` | `#875faf` | ||||
| `98` | `mediumpurple3_1` | `135,95,215` | `#875fd7` | ||||
| `99` | `slateblue1` | `135,95,255` | `#875fff` | ||||
| `100` | `yellow4` | `135,135,0` | `#878700` | ||||
| `101` | `wheat4` | `135,135,95` | `#87875f` | ||||
| `102` | `grey53` | `135,135,135` | `#878787` | ||||
| `103` | `lightslategrey` | `135,135,175` | `#8787af` | ||||
| `104` | `mediumpurple` | `135,135,215` | `#8787d7` | ||||
| `105` | `lightslateblue` | `135,135,255` | `#8787ff` | ||||
| `106` | `yellow4_1` | `135,175,0` | `#87af00` | ||||
| `107` | `darkolivegreen3` | `135,175,95` | `#87af5f` | ||||
| `108` | `darkseagreen` | `135,175,135` | `#87af87` | ||||
| `109` | `lightskyblue3` | `135,175,175` | `#87afaf` | ||||
| `110` | `lightskyblue3_1` | `135,175,215` | `#87afd7` | ||||
| `111` | `skyblue2` | `135,175,255` | `#87afff` | ||||
| `112` | `chartreuse2_1` | `135,215,0` | `#87d700` | ||||
| `113` | `darkolivegreen3_1` | `135,215,95` | `#87d75f` | ||||
| `114` | `palegreen3_1` | `135,215,135` | `#87d787` | ||||
| `115` | `darkseagreen3` | `135,215,175` | `#87d7af` | ||||
| `116` | `darkslategray3` | `135,215,215` | `#87d7d7` | ||||
| `117` | `skyblue1` | `135,215,255` | `#87d7ff` | ||||
| `118` | `chartreuse1` | `135,255,0` | `#87ff00` | ||||
| `119` | `lightgreen` | `135,255,95` | `#87ff5f` | ||||
| `120` | `lightgreen_1` | `135,255,135` | `#87ff87` | ||||
| `121` | `palegreen1` | `135,255,175` | `#87ffaf` | ||||
| `122` | `aquamarine1_1` | `135,255,215` | `#87ffd7` | ||||
| `123` | `darkslategray1` | `135,255,255` | `#87ffff` | ||||
| `124` | `red3` | `175,0,0` | `#af0000` | ||||
| `125` | `deeppink4_2` | `175,0,95` | `#af005f` | ||||
| `126` | `mediumvioletred` | `175,0,135` | `#af0087` | ||||
| `127` | `magenta3` | `175,0,175` | `#af00af` | ||||
| `128` | `darkviolet_1` | `175,0,215` | `#af00d7` | ||||
| `129` | `purple_2` | `175,0,255` | `#af00ff` | ||||
| `130` | `darkorange3` | `175,95,0` | `#af5f00` | ||||
| `131` | `indianred` | `175,95,95` | `#af5f5f` | ||||
| `132` | `hotpink3` | `175,95,135` | `#af5f87` | ||||
| `133` | `mediumorchid3` | `175,95,175` | `#af5faf` | ||||
| `134` | `mediumorchid` | `175,95,215` | `#af5fd7` | ||||
| `135` | `mediumpurple2` | `175,95,255` | `#af5fff` | ||||
| `136` | `darkgoldenrod` | `175,135,0` | `#af8700` | ||||
| `137` | `lightsalmon3` | `175,135,95` | `#af875f` | ||||
| `138` | `rosybrown` | `175,135,135` | `#af8787` | ||||
| `139` | `grey63` | `175,135,175` | `#af87af` | ||||
| `140` | `mediumpurple2_1` | `175,135,215` | `#af87d7` | ||||
| `141` | `mediumpurple1` | `175,135,255` | `#af87ff` | ||||
| `142` | `gold3` | `175,175,0` | `#afaf00` | ||||
| `143` | `darkkhaki` | `175,175,95` | `#afaf5f` | ||||
| `144` | `navajowhite3` | `175,175,135` | `#afaf87` | ||||
| `145` | `grey69` | `175,175,175` | `#afafaf` | ||||
| `146` | `lightsteelblue3` | `175,175,215` | `#afafd7` | ||||
| `147` | `lightsteelblue` | `175,175,255` | `#afafff` | ||||
| `148` | `yellow3` | `175,215,0` | `#afd700` | ||||
| `149` | `darkolivegreen3_2` | `175,215,95` | `#afd75f` | ||||
| `150` | `darkseagreen3_1` | `175,215,135` | `#afd787` | ||||
| `151` | `darkseagreen2` | `175,215,175` | `#afd7af` | ||||
| `152` | `lightcyan3` | `175,215,215` | `#afd7d7` | ||||
| `153` | `lightskyblue1` | `175,215,255` | `#afd7ff` | ||||
| `154` | `greenyellow` | `175,255,0` | `#afff00` | ||||
| `155` | `darkolivegreen2` | `175,255,95` | `#afff5f` | ||||
| `156` | `palegreen1_1` | `175,255,135` | `#afff87` | ||||
| `157` | `darkseagreen2_1` | `175,255,175` | `#afffaf` | ||||
| `158` | `darkseagreen1` | `175,255,215` | `#afffd7` | ||||
| `159` | `paleturquoise1` | `175,255,255` | `#afffff` | ||||
| `160` | `red3_1` | `215,0,0` | `#d70000` | ||||
| `161` | `deeppink3` | `215,0,95` | `#d7005f` | ||||
| `162` | `deeppink3_1` | `215,0,135` | `#d70087` | ||||
| `163` | `magenta3_1` | `215,0,175` | `#d700af` | ||||
| `164` | `magenta3_2` | `215,0,215` | `#d700d7` | ||||
| `165` | `magenta2` | `215,0,255` | `#d700ff` | ||||
| `166` | `darkorange3_1` | `215,95,0` | `#d75f00` | ||||
| `167` | `indianred_1` | `215,95,95` | `#d75f5f` | ||||
| `168` | `hotpink3_1` | `215,95,135` | `#d75f87` | ||||
| `169` | `hotpink2` | `215,95,175` | `#d75faf` | ||||
| `170` | `orchid` | `215,95,215` | `#d75fd7` | ||||
| `171` | `mediumorchid1` | `215,95,255` | `#d75fff` | ||||
| `172` | `orange3` | `215,135,0` | `#d78700` | ||||
| `173` | `lightsalmon3_1` | `215,135,95` | `#d7875f` | ||||
| `174` | `lightpink3` | `215,135,135` | `#d78787` | ||||
| `175` | `pink3` | `215,135,175` | `#d787af` | ||||
| `176` | `plum3` | `215,135,215` | `#d787d7` | ||||
| `177` | `violet` | `215,135,255` | `#d787ff` | ||||
| `178` | `gold3_1` | `215,175,0` | `#d7af00` | ||||
| `179` | `lightgoldenrod3` | `215,175,95` | `#d7af5f` | ||||
| `180` | `tan` | `215,175,135` | `#d7af87` | ||||
| `181` | `mistyrose3` | `215,175,175` | `#d7afaf` | ||||
| `182` | `thistle3` | `215,175,215` | `#d7afd7` | ||||
| `183` | `plum2` | `215,175,255` | `#d7afff` | ||||
| `184` | `yellow3_1` | `215,215,0` | `#d7d700` | ||||
| `185` | `khaki3` | `215,215,95` | `#d7d75f` | ||||
| `186` | `lightgoldenrod2` | `215,215,135` | `#d7d787` | ||||
| `187` | `lightyellow3` | `215,215,175` | `#d7d7af` | ||||
| `188` | `grey84` | `215,215,215` | `#d7d7d7` | ||||
| `189` | `lightsteelblue1` | `215,215,255` | `#d7d7ff` | ||||
| `190` | `yellow2` | `215,255,0` | `#d7ff00` | ||||
| `191` | `darkolivegreen1` | `215,255,95` | `#d7ff5f` | ||||
| `192` | `darkolivegreen1_1` | `215,255,135` | `#d7ff87` | ||||
| `193` | `darkseagreen1_1` | `215,255,175` | `#d7ffaf` | ||||
| `194` | `honeydew2` | `215,255,215` | `#d7ffd7` | ||||
| `195` | `lightcyan1` | `215,255,255` | `#d7ffff` | ||||
| `196` | `red1` | `255,0,0` | `#ff0000` | ||||
| `197` | `deeppink2` | `255,0,95` | `#ff005f` | ||||
| `198` | `deeppink1` | `255,0,135` | `#ff0087` | ||||
| `199` | `deeppink1_1` | `255,0,175` | `#ff00af` | ||||
| `200` | `magenta2_1` | `255,0,215` | `#ff00d7` | ||||
| `201` | `magenta1` | `255,0,255` | `#ff00ff` | ||||
| `202` | `orangered1` | `255,95,0` | `#ff5f00` | ||||
| `203` | `indianred1` | `255,95,95` | `#ff5f5f` | ||||
| `204` | `indianred1_1` | `255,95,135` | `#ff5f87` | ||||
| `205` | `hotpink` | `255,95,175` | `#ff5faf` | ||||
| `206` | `hotpink_1` | `255,95,215` | `#ff5fd7` | ||||
| `207` | `mediumorchid1_1` | `255,95,255` | `#ff5fff` | ||||
| `208` | `darkorange` | `255,135,0` | `#ff8700` | ||||
| `209` | `salmon1` | `255,135,95` | `#ff875f` | ||||
| `210` | `lightcoral` | `255,135,135` | `#ff8787` | ||||
| `211` | `palevioletred1` | `255,135,175` | `#ff87af` | ||||
| `212` | `orchid2` | `255,135,215` | `#ff87d7` | ||||
| `213` | `orchid1` | `255,135,255` | `#ff87ff` | ||||
| `214` | `orange1` | `255,175,0` | `#ffaf00` | ||||
| `215` | `sandybrown` | `255,175,95` | `#ffaf5f` | ||||
| `216` | `lightsalmon1` | `255,175,135` | `#ffaf87` | ||||
| `217` | `lightpink1` | `255,175,175` | `#ffafaf` | ||||
| `218` | `pink1` | `255,175,215` | `#ffafd7` | ||||
| `219` | `plum1` | `255,175,255` | `#ffafff` | ||||
| `220` | `gold1` | `255,215,0` | `#ffd700` | ||||
| `221` | `lightgoldenrod2_1` | `255,215,95` | `#ffd75f` | ||||
| `222` | `lightgoldenrod2_2` | `255,215,135` | `#ffd787` | ||||
| `223` | `navajowhite1` | `255,215,175` | `#ffd7af` | ||||
| `224` | `mistyrose1` | `255,215,215` | `#ffd7d7` | ||||
| `225` | `thistle1` | `255,215,255` | `#ffd7ff` | ||||
| `226` | `yellow1` | `255,255,0` | `#ffff00` | ||||
| `227` | `lightgoldenrod1` | `255,255,95` | `#ffff5f` | ||||
| `228` | `khaki1` | `255,255,135` | `#ffff87` | ||||
| `229` | `wheat1` | `255,255,175` | `#ffffaf` | ||||
| `230` | `cornsilk1` | `255,255,215` | `#ffffd7` | ||||
| `231` | `grey100` | `255,255,255` | `#ffffff` | ||||
| `232` | `grey3` | `8,8,8` | `#080808` | ||||
| `233` | `grey7` | `18,18,18` | `#121212` | ||||
| `234` | `grey11` | `28,28,28` | `#1c1c1c` | ||||
| `235` | `grey15` | `38,38,38` | `#262626` | ||||
| `236` | `grey19` | `48,48,48` | `#303030` | ||||
| `237` | `grey23` | `58,58,58` | `#3a3a3a` | ||||
| `238` | `grey27` | `68,68,68` | `#444444` | ||||
| `239` | `grey30` | `78,78,78` | `#4e4e4e` | ||||
| `240` | `grey35` | `88,88,88` | `#585858` | ||||
| `241` | `grey39` | `98,98,98` | `#626262` | ||||
| `242` | `grey42` | `108,108,108` | `#6c6c6c` | ||||
| `243` | `grey46` | `118,118,118` | `#767676` | ||||
| `244` | `grey50` | `128,128,128` | `#808080` | ||||
| `245` | `grey54` | `138,138,138` | `#8a8a8a` | ||||
| `246` | `grey58` | `148,148,148` | `#949494` | ||||
| `247` | `grey62` | `158,158,158` | `#9e9e9e` | ||||
| `248` | `grey66` | `168,168,168` | `#a8a8a8` | ||||
| `249` | `grey70` | `178,178,178` | `#b2b2b2` | ||||
| `250` | `grey74` | `188,188,188` | `#bcbcbc` | ||||
| `251` | `grey78` | `198,198,198` | `#c6c6c6` | ||||
| `252` | `grey82` | `208,208,208` | `#d0d0d0` | ||||
| `253` | `grey85` | `218,218,218` | `#dadada` | ||||
| `254` | `grey89` | `228,228,228` | `#e4e4e4` | ||||
| `255` | `grey93` | `238,238,238` | `#eeeeee` | ||||
|   | ||||
| @@ -12,9 +12,9 @@ namespace Sample | ||||
|             AnsiConsole.Style = Styles.Underline | Styles.Bold; | ||||
|             AnsiConsole.WriteLine("Hello World!"); | ||||
|             AnsiConsole.Reset(); | ||||
|             AnsiConsole.WriteLine("Capabilities: {0}", AnsiConsole.Capabilities); | ||||
|             AnsiConsole.MarkupLine("Capabilities: [yellow underline]{0}[/]", AnsiConsole.Capabilities); | ||||
|             AnsiConsole.WriteLine($"Width={AnsiConsole.Width}, Height={AnsiConsole.Height}"); | ||||
|             AnsiConsole.WriteLine("Good bye!"); | ||||
|             AnsiConsole.MarkupLine("[white on red]Good[/] [red]bye[/]!"); | ||||
|             AnsiConsole.WriteLine(); | ||||
|  | ||||
|             // We can get the default console via the static API. | ||||
| @@ -37,7 +37,7 @@ namespace Sample | ||||
|             console.ResetColors(); | ||||
|             console.ResetStyle(); | ||||
|             console.WriteLine("Capabilities: {0}", AnsiConsole.Capabilities); | ||||
|             console.WriteLine($"Width={AnsiConsole.Width}, Height={AnsiConsole.Height}"); | ||||
|             console.MarkupLine("Width=[yellow]{0}[/], Height=[yellow]{1}[/]", AnsiConsole.Width, AnsiConsole.Height); | ||||
|             console.WriteLine("Good bye!"); | ||||
|             console.WriteLine(); | ||||
|         } | ||||
|   | ||||
							
								
								
									
										89
									
								
								src/Spectre.Console.Tests/AnsiConsoleTests.Markup.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/Spectre.Console.Tests/AnsiConsoleTests.Markup.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
| using Shouldly; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests | ||||
| { | ||||
|     public partial class AnsiConsoleTests | ||||
|     { | ||||
|         [SuppressMessage("Naming", "CA1724:Type names should not match namespaces")] | ||||
|         public sealed class Markup | ||||
|         { | ||||
|             [Theory] | ||||
|             [InlineData("[yellow]Hello[/]", "[93mHello[0m")] | ||||
|             [InlineData("[yellow]Hello [italic]World[/]![/]", "[93mHello [0m[3;93mWorld[0m[93m![0m")] | ||||
|             public void Should_Output_Expected_Ansi_For_Markup(string markup, string expected) | ||||
|             { | ||||
|                 // Given | ||||
|                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); | ||||
|  | ||||
|                 // When | ||||
|                 fixture.Console.Markup(markup); | ||||
|  | ||||
|                 // Then | ||||
|                 fixture.Output.ShouldBe(expected); | ||||
|             } | ||||
|  | ||||
|             [Theory] | ||||
|             [InlineData("[yellow]Hello [[ World[/]", "[93mHello [0m[93m[[0m[93m World[0m")] | ||||
|             public void Should_Be_Able_To_Escape_Tags(string markup, string expected) | ||||
|             { | ||||
|                 // Given | ||||
|                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); | ||||
|  | ||||
|                 // When | ||||
|                 fixture.Console.Markup(markup); | ||||
|  | ||||
|                 // Then | ||||
|                 fixture.Output.ShouldBe(expected); | ||||
|             } | ||||
|  | ||||
|             [Theory] | ||||
|             [InlineData("[yellow]Hello[", "Encountered malformed markup tag at position 14.")] | ||||
|             [InlineData("[yellow]Hello[/", "Encountered malformed markup tag at position 15.")] | ||||
|             [InlineData("[yellow]Hello[/foo", "Encountered malformed markup tag at position 15.")] | ||||
|             [InlineData("[yellow Hello", "Encountered malformed markup tag at position 13.")] | ||||
|             public void Should_Throw_If_Encounters_Malformed_Tag(string markup, string expected) | ||||
|             { | ||||
|                 // Given | ||||
|                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); | ||||
|  | ||||
|                 // When | ||||
|                 var result = Record.Exception(() => fixture.Console.Markup(markup)); | ||||
|  | ||||
|                 // Then | ||||
|                 result.ShouldBeOfType<InvalidOperationException>() | ||||
|                     .Message.ShouldBe(expected); | ||||
|             } | ||||
|  | ||||
|             [Fact] | ||||
|             public void Should_Throw_If_Tags_Are_Unbalanced() | ||||
|             { | ||||
|                 // Given | ||||
|                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); | ||||
|  | ||||
|                 // When | ||||
|                 var result = Record.Exception(() => fixture.Console.Markup("[yellow][blue]Hello[/]")); | ||||
|  | ||||
|                 // Then | ||||
|                 result.ShouldBeOfType<InvalidOperationException>() | ||||
|                     .Message.ShouldBe("Unbalanced markup stack. Did you forget to close a tag?"); | ||||
|             } | ||||
|  | ||||
|             [Fact] | ||||
|             public void Should_Throw_If_Encounters_Closing_Tag() | ||||
|             { | ||||
|                 // Given | ||||
|                 var fixture = new AnsiConsoleFixture(ColorSystem.Standard, AnsiSupport.Yes); | ||||
|  | ||||
|                 // When | ||||
|                 var result = Record.Exception(() => fixture.Console.Markup("Hello[/]World")); | ||||
|  | ||||
|                 // Then | ||||
|                 result.ShouldBeOfType<InvalidOperationException>() | ||||
|                     .Message.ShouldBe("Encountered closing tag when none was expected near position 5."); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										52
									
								
								src/Spectre.Console/AnsiConsole.Markup.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/Spectre.Console/AnsiConsole.Markup.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A console capable of writing ANSI escape sequences. | ||||
|     /// </summary> | ||||
|     public static partial class AnsiConsole | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Writes the specified markup to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="format">A composite format string.</param> | ||||
|         /// <param name="args">An array of objects to write.</param> | ||||
|         public static void Markup(string format, params object[] args) | ||||
|         { | ||||
|             Console.Markup(format, args); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Writes the specified markup to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="provider">An object that supplies culture-specific formatting information.</param> | ||||
|         /// <param name="format">A composite format string.</param> | ||||
|         /// <param name="args">An array of objects to write.</param> | ||||
|         public static void Markup(IFormatProvider provider, string format, params object[] args) | ||||
|         { | ||||
|             Console.Markup(provider, format, args); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Writes the specified markup, followed by the current line terminator, to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="format">A composite format string.</param> | ||||
|         /// <param name="args">An array of objects to write.</param> | ||||
|         public static void MarkupLine(string format, params object[] args) | ||||
|         { | ||||
|             Console.MarkupLine(format, args); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Writes the specified markup, followed by the current line terminator, to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="provider">An object that supplies culture-specific formatting information.</param> | ||||
|         /// <param name="format">A composite format string.</param> | ||||
|         /// <param name="args">An array of objects to write.</param> | ||||
|         public static void MarkupLine(IFormatProvider provider, string format, params object[] args) | ||||
|         { | ||||
|             Console.MarkupLine(provider, format, args); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -3,8 +3,7 @@ using System.IO; | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Settings used by <see cref="ConsoleBuilder"/> | ||||
|     /// when building a <see cref="IAnsiConsole"/>. | ||||
|     /// Settings used when building a <see cref="IAnsiConsole"/>. | ||||
|     /// </summary> | ||||
|     public sealed class AnsiConsoleSettings | ||||
|     { | ||||
|   | ||||
							
								
								
									
										60
									
								
								src/Spectre.Console/ConsoleExtensions.Markup.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								src/Spectre.Console/ConsoleExtensions.Markup.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| using System; | ||||
| using System.Globalization; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="IAnsiConsole"/>. | ||||
|     /// </summary> | ||||
|     public static partial class ConsoleExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Writes the specified markup to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console to write to.</param> | ||||
|         /// <param name="format">A composite format string.</param> | ||||
|         /// <param name="args">An array of objects to write.</param> | ||||
|         public static void Markup(this IAnsiConsole console, string format, params object[] args) | ||||
|         { | ||||
|             Markup(console, CultureInfo.CurrentCulture, format, args); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Writes the specified markup to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console to write to.</param> | ||||
|         /// <param name="provider">An object that supplies culture-specific formatting information.</param> | ||||
|         /// <param name="format">A composite format string.</param> | ||||
|         /// <param name="args">An array of objects to write.</param> | ||||
|         public static void Markup(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args) | ||||
|         { | ||||
|             var result = MarkupParser.Parse(string.Format(provider, format, args)); | ||||
|             result.Render(console); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Writes the specified markup, followed by the current line terminator, to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console to write to.</param> | ||||
|         /// <param name="format">A composite format string.</param> | ||||
|         /// <param name="args">An array of objects to write.</param> | ||||
|         public static void MarkupLine(this IAnsiConsole console, string format, params object[] args) | ||||
|         { | ||||
|             MarkupLine(console, CultureInfo.CurrentCulture, format, args); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Writes the specified markup, followed by the current line terminator, to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console to write to.</param> | ||||
|         /// <param name="provider">An object that supplies culture-specific formatting information.</param> | ||||
|         /// <param name="format">A composite format string.</param> | ||||
|         /// <param name="args">An array of objects to write.</param> | ||||
|         public static void MarkupLine(this IAnsiConsole console, IFormatProvider provider, string format, params object[] args) | ||||
|         { | ||||
|             Markup(console, provider, format, args); | ||||
|             console.WriteLine(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,73 +0,0 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class Composer : IRenderable | ||||
|     { | ||||
|         private readonly BlockElement _root; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Length => _root.Length; | ||||
|  | ||||
|         public Composer() | ||||
|         { | ||||
|             _root = new BlockElement(); | ||||
|         } | ||||
|  | ||||
|         public static Composer New() | ||||
|         { | ||||
|             return new Composer(); | ||||
|         } | ||||
|  | ||||
|         public Composer Text(string text) | ||||
|         { | ||||
|             _root.Append(new TextElement(text)); | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public Composer Foreground(Color color, Action<Composer> action) | ||||
|         { | ||||
|             if (action is null) | ||||
|             { | ||||
|                 return this; | ||||
|             } | ||||
|  | ||||
|             var content = new Composer(); | ||||
|             action(content); | ||||
|             _root.Append(new ForegroundElement(color, content)); | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public Composer Background(Color color, Action<Composer> action) | ||||
|         { | ||||
|             if (action is null) | ||||
|             { | ||||
|                 return this; | ||||
|             } | ||||
|  | ||||
|             var content = new Composer(); | ||||
|             action(content); | ||||
|             _root.Append(new BackgroundElement(color, content)); | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         public Composer Style(Styles style, Action<Composer> action) | ||||
|         { | ||||
|             if (action is null) | ||||
|             { | ||||
|                 return this; | ||||
|             } | ||||
|  | ||||
|             var content = new Composer(); | ||||
|             action(content); | ||||
|             _root.Append(new StyleElement(style, content)); | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             _root.Render(renderer); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")] | ||||
|     internal sealed class BackgroundElement : IRenderable | ||||
|     { | ||||
|         private readonly Color _color; | ||||
|         private readonly IRenderable _element; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Length => _element.Length; | ||||
|  | ||||
|         public BackgroundElement(Color color, IRenderable element) | ||||
|         { | ||||
|             _color = color; | ||||
|             _element = element ?? throw new ArgumentNullException(nameof(element)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             if (renderer is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(renderer)); | ||||
|             } | ||||
|  | ||||
|             using (renderer.PushColor(_color, foreground: false)) | ||||
|             { | ||||
|                 _element.Render(renderer); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,41 +0,0 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")] | ||||
|     internal sealed class BlockElement : IRenderable | ||||
|     { | ||||
|         private readonly List<IRenderable> _elements; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Length { get; private set; } | ||||
|  | ||||
|         public IReadOnlyList<IRenderable> Elements => _elements; | ||||
|  | ||||
|         public BlockElement() | ||||
|         { | ||||
|             _elements = new List<IRenderable>(); | ||||
|         } | ||||
|  | ||||
|         public BlockElement Append(IRenderable element) | ||||
|         { | ||||
|             if (element != null) | ||||
|             { | ||||
|                 _elements.Add(element); | ||||
|                 Length += element.Length; | ||||
|             } | ||||
|  | ||||
|             return this; | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             foreach (var element in _elements) | ||||
|             { | ||||
|                 element.Render(renderer); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")] | ||||
|     internal sealed class ForegroundElement : IRenderable | ||||
|     { | ||||
|         private readonly Color _color; | ||||
|         private readonly IRenderable _element; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Length => _element.Length; | ||||
|  | ||||
|         public ForegroundElement(Color color, IRenderable element) | ||||
|         { | ||||
|             _color = color; | ||||
|             _element = element ?? throw new ArgumentNullException(nameof(element)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             if (renderer is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(renderer)); | ||||
|             } | ||||
|  | ||||
|             using (renderer.PushColor(_color, foreground: true)) | ||||
|             { | ||||
|                 _element.Render(renderer); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,18 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")] | ||||
|     internal sealed class LineBreakElement : IRenderable | ||||
|     { | ||||
|         /// <inheritdoc/> | ||||
|         public int Length => 0; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             renderer.Write(Environment.NewLine); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,35 +0,0 @@ | ||||
| using System; | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")] | ||||
|     internal sealed class StyleElement : IRenderable | ||||
|     { | ||||
|         private readonly Styles _style; | ||||
|         private readonly IRenderable _element; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Length => _element.Length; | ||||
|  | ||||
|         public StyleElement(Styles style, IRenderable element) | ||||
|         { | ||||
|             _style = style; | ||||
|             _element = element ?? throw new ArgumentNullException(nameof(element)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             if (renderer is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(renderer)); | ||||
|             } | ||||
|  | ||||
|             using (renderer.PushStyle(_style)) | ||||
|             { | ||||
|                 _element.Render(renderer); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,24 +0,0 @@ | ||||
| using System.Diagnostics.CodeAnalysis; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     [SuppressMessage("Performance", "CA1812:Avoid uninstantiated internal classes", Justification = "Not used (yet)")] | ||||
|     internal sealed class TextElement : IRenderable | ||||
|     { | ||||
|         private readonly string _text; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Length => _text.Length; | ||||
|  | ||||
|         public TextElement(string text) | ||||
|         { | ||||
|             _text = text ?? throw new System.ArgumentNullException(nameof(text)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             renderer.Write(_text); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,19 +0,0 @@ | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents something that can be rendered to a console. | ||||
|     /// </summary> | ||||
|     internal interface IRenderable | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Gets the length of the element. | ||||
|         /// </summary> | ||||
|         int Length { get; } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Renders the element using the specified renderer. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The renderer to use.</param> | ||||
|         void Render(IAnsiConsole console); | ||||
|     } | ||||
| } | ||||
| @@ -1,7 +1,6 @@ | ||||
| using System; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class ConsoleBuilder | ||||
|     { | ||||
|   | ||||
| @@ -12,7 +12,7 @@ namespace Spectre.Console.Internal | ||||
|                 throw new ArgumentNullException(nameof(console)); | ||||
|             } | ||||
|  | ||||
|             var current = console.Foreground; | ||||
|             var current = foreground ? console.Foreground : console.Background; | ||||
|             console.SetColor(color, foreground); | ||||
|             return new ColorScope(console, current, foreground); | ||||
|         } | ||||
|   | ||||
							
								
								
									
										48
									
								
								src/Spectre.Console/Internal/Lookup.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										48
									
								
								src/Spectre.Console/Internal/Lookup.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,48 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class Lookup | ||||
|     { | ||||
|         private readonly Dictionary<string, Styles?> _styles; | ||||
|         private readonly Dictionary<string, Color?> _colors; | ||||
|  | ||||
|         private static readonly Lazy<Lookup> _lazy = new Lazy<Lookup>(() => new Lookup()); | ||||
|         public static Lookup Instance => _lazy.Value; | ||||
|  | ||||
|         private Lookup() | ||||
|         { | ||||
|             _styles = new Dictionary<string, Styles?>(StringComparer.OrdinalIgnoreCase) | ||||
|             { | ||||
|                 { "bold", Styles.Bold }, | ||||
|                 { "dim", Styles.Dim }, | ||||
|                 { "italic", Styles.Italic }, | ||||
|                 { "underline", Styles.Underline }, | ||||
|                 { "invert", Styles.Invert }, | ||||
|                 { "conceal", Styles.Conceal }, | ||||
|                 { "slowblink", Styles.SlowBlink }, | ||||
|                 { "rapidblink", Styles.RapidBlink }, | ||||
|                 { "strikethrough", Styles.Strikethrough }, | ||||
|             }; | ||||
|  | ||||
|             _colors = new Dictionary<string, Color?>(StringComparer.OrdinalIgnoreCase); | ||||
|             foreach (var color in ColorPalette.EightBit) | ||||
|             { | ||||
|                 _colors.Add(color.Name, color); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public Styles? GetStyle(string name) | ||||
|         { | ||||
|             _styles.TryGetValue(name, out var style); | ||||
|             return style; | ||||
|         } | ||||
|  | ||||
|         public Color? GetColor(string name) | ||||
|         { | ||||
|             _colors.TryGetValue(name, out var color); | ||||
|             return color; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										30
									
								
								src/Spectre.Console/Internal/Markup/Ast/MarkupBlockNode.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/Spectre.Console/Internal/Markup/Ast/MarkupBlockNode.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class MarkupBlockNode : IMarkupNode | ||||
|     { | ||||
|         private readonly List<IMarkupNode> _elements; | ||||
|  | ||||
|         public MarkupBlockNode() | ||||
|         { | ||||
|             _elements = new List<IMarkupNode>(); | ||||
|         } | ||||
|  | ||||
|         public void Append(IMarkupNode element) | ||||
|         { | ||||
|             if (element != null) | ||||
|             { | ||||
|                 _elements.Add(element); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             foreach (var element in _elements) | ||||
|             { | ||||
|                 element.Render(renderer); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										52
									
								
								src/Spectre.Console/Internal/Markup/Ast/MarkupStyleNode.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/Spectre.Console/Internal/Markup/Ast/MarkupStyleNode.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,52 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class MarkupStyleNode : IMarkupNode | ||||
|     { | ||||
|         private readonly Styles? _style; | ||||
|         private readonly Color? _foreground; | ||||
|         private readonly Color? _background; | ||||
|         private readonly IMarkupNode _element; | ||||
|  | ||||
|         public MarkupStyleNode( | ||||
|             Styles? style, | ||||
|             Color? foreground, | ||||
|             Color? background, | ||||
|             IMarkupNode element) | ||||
|         { | ||||
|             _style = style; | ||||
|             _foreground = foreground; | ||||
|             _background = background; | ||||
|             _element = element ?? throw new ArgumentNullException(nameof(element)); | ||||
|         } | ||||
|  | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             var style = (IDisposable)null; | ||||
|             var foreground = (IDisposable)null; | ||||
|             var background = (IDisposable)null; | ||||
|  | ||||
|             if (_style != null) | ||||
|             { | ||||
|                 style = renderer.PushStyle(_style.Value); | ||||
|             } | ||||
|  | ||||
|             if (_foreground != null) | ||||
|             { | ||||
|                 foreground = renderer.PushColor(_foreground.Value, foreground: true); | ||||
|             } | ||||
|  | ||||
|             if (_background != null) | ||||
|             { | ||||
|                 background = renderer.PushColor(_background.Value, foreground: false); | ||||
|             } | ||||
|  | ||||
|             _element.Render(renderer); | ||||
|  | ||||
|             background?.Dispose(); | ||||
|             foreground?.Dispose(); | ||||
|             style?.Dispose(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										19
									
								
								src/Spectre.Console/Internal/Markup/Ast/MarkupTextNode.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/Spectre.Console/Internal/Markup/Ast/MarkupTextNode.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class MarkupTextNode : IMarkupNode | ||||
|     { | ||||
|         public string Text { get; } | ||||
|  | ||||
|         public MarkupTextNode(string text) | ||||
|         { | ||||
|             Text = text ?? throw new ArgumentNullException(nameof(text)); | ||||
|         } | ||||
|  | ||||
|         public void Render(IAnsiConsole renderer) | ||||
|         { | ||||
|             renderer.Write(Text); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										14
									
								
								src/Spectre.Console/Internal/Markup/IMarkupNode.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								src/Spectre.Console/Internal/Markup/IMarkupNode.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a parsed markup node. | ||||
|     /// </summary> | ||||
|     internal interface IMarkupNode | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Renders the node using the specified renderer. | ||||
|         /// </summary> | ||||
|         /// <param name="renderer">The renderer to use.</param> | ||||
|         void Render(IAnsiConsole renderer); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										72
									
								
								src/Spectre.Console/Internal/Markup/MarkupParser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										72
									
								
								src/Spectre.Console/Internal/Markup/MarkupParser.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,72 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class MarkupParser | ||||
|     { | ||||
|         public static IMarkupNode Parse(string text) | ||||
|         { | ||||
|             using var tokenizer = new MarkupTokenizer(text); | ||||
|             var root = new MarkupBlockNode(); | ||||
|  | ||||
|             var stack = new Stack<MarkupBlockNode>(); | ||||
|             var current = root; | ||||
|  | ||||
|             while (true) | ||||
|             { | ||||
|                 var token = tokenizer.GetNext(); | ||||
|                 if (token == null) | ||||
|                 { | ||||
|                     break; | ||||
|                 } | ||||
|  | ||||
|                 if (token.Kind == MarkupTokenKind.Text) | ||||
|                 { | ||||
|                     current.Append(new MarkupTextNode(token.Value)); | ||||
|                     continue; | ||||
|                 } | ||||
|                 else if (token.Kind == MarkupTokenKind.Open) | ||||
|                 { | ||||
|                     var (style, foreground, background) = MarkupStyleParser.Parse(token.Value); | ||||
|                     var content = new MarkupBlockNode(); | ||||
|                     current.Append(new MarkupStyleNode(style, foreground, background, content)); | ||||
|  | ||||
|                     current = content; | ||||
|                     stack.Push(current); | ||||
|  | ||||
|                     continue; | ||||
|                 } | ||||
|                 else if (token.Kind == MarkupTokenKind.Close) | ||||
|                 { | ||||
|                     if (stack.Count == 0) | ||||
|                     { | ||||
|                         throw new InvalidOperationException($"Encountered closing tag when none was expected near position {token.Position}."); | ||||
|                     } | ||||
|  | ||||
|                     stack.Pop(); | ||||
|  | ||||
|                     if (stack.Count == 0) | ||||
|                     { | ||||
|                         current = root; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         current = stack.Peek(); | ||||
|                     } | ||||
|  | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 throw new InvalidOperationException("Encountered unkown markup token."); | ||||
|             } | ||||
|  | ||||
|             if (stack.Count > 0) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Unbalanced markup stack. Did you forget to close a tag?"); | ||||
|             } | ||||
|  | ||||
|             return root; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										65
									
								
								src/Spectre.Console/Internal/Markup/MarkupStyleParser.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										65
									
								
								src/Spectre.Console/Internal/Markup/MarkupStyleParser.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,65 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal static class MarkupStyleParser | ||||
|     { | ||||
|         public static (Styles? Style, Color? Foreground, Color? Background) Parse(string text) | ||||
|         { | ||||
|             var effectiveStyle = (Styles?)null; | ||||
|             var effectiveForeground = (Color?)null; | ||||
|             var effectiveBackground = (Color?)null; | ||||
|  | ||||
|             var parts = text.Split(new[] { ' ' }); | ||||
|             var foreground = true; | ||||
|             foreach (var part in parts) | ||||
|             { | ||||
|                 if (part.Equals("on", StringComparison.OrdinalIgnoreCase)) | ||||
|                 { | ||||
|                     foreground = false; | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var style = Lookup.Instance.GetStyle(part); | ||||
|                 if (style != null) | ||||
|                 { | ||||
|                     if (effectiveStyle == null) | ||||
|                     { | ||||
|                         effectiveStyle = Styles.None; | ||||
|                     } | ||||
|  | ||||
|                     effectiveStyle |= style.Value; | ||||
|                 } | ||||
|                 else | ||||
|                 { | ||||
|                     var color = Lookup.Instance.GetColor(part); | ||||
|                     if (color == null) | ||||
|                     { | ||||
|                         throw new InvalidOperationException("Could not find color.."); | ||||
|                     } | ||||
|  | ||||
|                     if (foreground) | ||||
|                     { | ||||
|                         if (effectiveForeground != null) | ||||
|                         { | ||||
|                             throw new InvalidOperationException("A foreground has already been set."); | ||||
|                         } | ||||
|  | ||||
|                         effectiveForeground = color; | ||||
|                     } | ||||
|                     else | ||||
|                     { | ||||
|                         if (effectiveBackground != null) | ||||
|                         { | ||||
|                             throw new InvalidOperationException("A background has already been set."); | ||||
|                         } | ||||
|  | ||||
|                         effectiveBackground = color; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             return (effectiveStyle, effectiveForeground, effectiveBackground); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										18
									
								
								src/Spectre.Console/Internal/Markup/MarkupToken.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/Spectre.Console/Internal/Markup/MarkupToken.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class MarkupToken | ||||
|     { | ||||
|         public MarkupTokenKind Kind { get; } | ||||
|         public string Value { get; } | ||||
|         public int Position { get; set; } | ||||
|  | ||||
|         public MarkupToken(MarkupTokenKind kind, string value, int position) | ||||
|         { | ||||
|             Kind = kind; | ||||
|             Value = value ?? throw new ArgumentNullException(nameof(value)); | ||||
|             Position = position; | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										9
									
								
								src/Spectre.Console/Internal/Markup/MarkupTokenKind.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								src/Spectre.Console/Internal/Markup/MarkupTokenKind.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal enum MarkupTokenKind | ||||
|     { | ||||
|         Text = 0, | ||||
|         Open, | ||||
|         Close, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										104
									
								
								src/Spectre.Console/Internal/Markup/MarkupTokenizer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										104
									
								
								src/Spectre.Console/Internal/Markup/MarkupTokenizer.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,104 @@ | ||||
| using System; | ||||
| using System.Text; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class MarkupTokenizer : IDisposable | ||||
|     { | ||||
|         private readonly StringBuffer _reader; | ||||
|  | ||||
|         public MarkupTokenizer(string text) | ||||
|         { | ||||
|             _reader = new StringBuffer(text ?? throw new ArgumentNullException(nameof(text))); | ||||
|         } | ||||
|  | ||||
|         public void Dispose() | ||||
|         { | ||||
|             _reader.Dispose(); | ||||
|         } | ||||
|  | ||||
|         public MarkupToken GetNext() | ||||
|         { | ||||
|             if (_reader.Eof) | ||||
|             { | ||||
|                 return null; | ||||
|             } | ||||
|  | ||||
|             var current = _reader.Peek(); | ||||
|             if (current == '[') | ||||
|             { | ||||
|                 var position = _reader.Position; | ||||
|  | ||||
|                 _reader.Read(); | ||||
|  | ||||
|                 if (_reader.Eof) | ||||
|                 { | ||||
|                     throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}."); | ||||
|                 } | ||||
|  | ||||
|                 current = _reader.Peek(); | ||||
|                 if (current == '[') | ||||
|                 { | ||||
|                     _reader.Read(); | ||||
|                     return new MarkupToken(MarkupTokenKind.Text, "[", position); | ||||
|                 } | ||||
|  | ||||
|                 if (current == '/') | ||||
|                 { | ||||
|                     _reader.Read(); | ||||
|  | ||||
|                     if (_reader.Eof) | ||||
|                     { | ||||
|                         throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}."); | ||||
|                     } | ||||
|  | ||||
|                     current = _reader.Peek(); | ||||
|                     if (current != ']') | ||||
|                     { | ||||
|                         throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}."); | ||||
|                     } | ||||
|  | ||||
|                     _reader.Read(); | ||||
|                     return new MarkupToken(MarkupTokenKind.Close, string.Empty, position); | ||||
|                 } | ||||
|  | ||||
|                 var builder = new StringBuilder(); | ||||
|                 while (!_reader.Eof) | ||||
|                 { | ||||
|                     current = _reader.Peek(); | ||||
|                     if (current == ']') | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     builder.Append(_reader.Read()); | ||||
|                 } | ||||
|  | ||||
|                 if (_reader.Eof) | ||||
|                 { | ||||
|                     throw new InvalidOperationException($"Encountered malformed markup tag at position {_reader.Position}."); | ||||
|                 } | ||||
|  | ||||
|                 _reader.Read(); | ||||
|                 return new MarkupToken(MarkupTokenKind.Open, builder.ToString(), position); | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|                 var position = _reader.Position; | ||||
|                 var builder = new StringBuilder(); | ||||
|                 while (!_reader.Eof) | ||||
|                 { | ||||
|                     current = _reader.Peek(); | ||||
|                     if (current == '[') | ||||
|                     { | ||||
|                         break; | ||||
|                     } | ||||
|  | ||||
|                     builder.Append(_reader.Read()); | ||||
|                 } | ||||
|  | ||||
|                 return new MarkupToken(MarkupTokenKind.Text, builder.ToString(), position); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										50
									
								
								src/Spectre.Console/Internal/Markup/StringBuffer.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/Spectre.Console/Internal/Markup/StringBuffer.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,50 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class StringBuffer : IDisposable | ||||
|     { | ||||
|         private readonly StringReader _reader; | ||||
|         private readonly int _length; | ||||
|  | ||||
|         public int Position { get; private set; } | ||||
|         public bool Eof => Position >= _length; | ||||
|  | ||||
|         public StringBuffer(string text) | ||||
|         { | ||||
|             text ??= string.Empty; | ||||
|  | ||||
|             _reader = new StringReader(text); | ||||
|             _length = text.Length; | ||||
|  | ||||
|             Position = 0; | ||||
|         } | ||||
|  | ||||
|         public void Dispose() | ||||
|         { | ||||
|             _reader.Dispose(); | ||||
|         } | ||||
|  | ||||
|         public char Peek() | ||||
|         { | ||||
|             if (Eof) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Tried to peek past the end of the text."); | ||||
|             } | ||||
|  | ||||
|             return (char)_reader.Peek(); | ||||
|         } | ||||
|  | ||||
|         public char Read() | ||||
|         { | ||||
|             if (Eof) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Tried to read past the end of the text."); | ||||
|             } | ||||
|  | ||||
|             Position++; | ||||
|             return (char)_reader.Read(); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user