mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-10-31 09:09:25 +08:00 
			
		
		
		
	Add support for recording console output
This commit adds support for recording console output as well as exporting it to either text or HTML. A user can also provide their own encoder if they wish.
This commit is contained in:
		 Patrik Svensson
					Patrik Svensson
				
			
				
					committed by
					
						 Patrik Svensson
						Patrik Svensson
					
				
			
			
				
	
			
			
			 Patrik Svensson
						Patrik Svensson
					
				
			
						parent
						
							b197f278ed
						
					
				
				
					commit
					cd0d182f12
				
			| @@ -1,6 +1,7 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
| using Spectre.Console.Tests.Tools; | ||||
|  | ||||
| namespace Spectre.Console.Tests | ||||
| @@ -36,9 +37,9 @@ namespace Spectre.Console.Tests | ||||
|             _writer?.Dispose(); | ||||
|         } | ||||
|  | ||||
|         public void Write(string text, Style style) | ||||
|         public void Write(Segment segment) | ||||
|         { | ||||
|             _console.Write(text, style); | ||||
|             _console.Write(segment); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -2,6 +2,7 @@ using System; | ||||
| using System.Collections.Generic; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Tests | ||||
| { | ||||
| @@ -40,9 +41,14 @@ namespace Spectre.Console.Tests | ||||
|             Writer.Dispose(); | ||||
|         } | ||||
|  | ||||
|         public void Write(string text, Style style) | ||||
|         public void Write(Segment segment) | ||||
|         { | ||||
|             Writer.Write(text); | ||||
|             if (segment is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(segment)); | ||||
|             } | ||||
|  | ||||
|             Writer.Write(segment.Text); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										66
									
								
								src/Spectre.Console.Tests/Unit/RecorderTests.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/Spectre.Console.Tests/Unit/RecorderTests.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| using Shouldly; | ||||
| using Xunit; | ||||
|  | ||||
| namespace Spectre.Console.Tests.Unit | ||||
| { | ||||
|     public sealed class RecorderTests | ||||
|     { | ||||
|         [Fact] | ||||
|         public void Should_Export_Text_As_Expected() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(); | ||||
|             var recorder = new Recorder(console); | ||||
|  | ||||
|             recorder.Render(new Table() | ||||
|                 .AddColumns("Foo", "Bar", "Qux") | ||||
|                 .AddRow("Corgi", "Waldo", "Zap") | ||||
|                 .AddRow(new Panel("Hello World").RoundedBorder())); | ||||
|  | ||||
|             // When | ||||
|             var result = recorder.ExportText().Split(new[] { '\n' }); | ||||
|  | ||||
|             // Then | ||||
|             result.Length.ShouldBe(8); | ||||
|             result[0].ShouldBe("┌─────────────────┬───────┬─────┐"); | ||||
|             result[1].ShouldBe("│ Foo             │ Bar   │ Qux │"); | ||||
|             result[2].ShouldBe("├─────────────────┼───────┼─────┤"); | ||||
|             result[3].ShouldBe("│ Corgi           │ Waldo │ Zap │"); | ||||
|             result[4].ShouldBe("│ ╭─────────────╮ │       │     │"); | ||||
|             result[5].ShouldBe("│ │ Hello World │ │       │     │"); | ||||
|             result[6].ShouldBe("│ ╰─────────────╯ │       │     │"); | ||||
|             result[7].ShouldBe("└─────────────────┴───────┴─────┘"); | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Export_Html_As_Expected() | ||||
|         { | ||||
|             // Given | ||||
|             var console = new PlainConsole(); | ||||
|             var recorder = new Recorder(console); | ||||
|  | ||||
|             recorder.Render(new Table() | ||||
|                 .AddColumns("[red on black]Foo[/]", "[green bold]Bar[/]", "[blue italic]Qux[/]") | ||||
|                 .AddRow("[invert underline]Corgi[/]", "[bold strikethrough]Waldo[/]", "[dim]Zap[/]") | ||||
|                 .AddRow(new Panel("[blue]Hello World[/]") | ||||
|                     .SetBorderColor(Color.Red).RoundedBorder())); | ||||
|  | ||||
|             // When | ||||
|             var html = recorder.ExportHtml(); | ||||
|             var result = html.Split(new[] { '\n' }); | ||||
|  | ||||
|             // Then | ||||
|             result.Length.ShouldBe(10); | ||||
|             result[0].ShouldBe("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">"); | ||||
|             result[1].ShouldBe("<span>┌─────────────────┬───────┬─────┐</span>"); | ||||
|             result[2].ShouldBe("<span>│ </span><span style=\"color: #FF0000;background-color: #000000\">Foo</span><span>             │ </span><span style=\"color: #008000;font-weight: bold;font-style: italic\">Bar</span><span>   │ </span><span style=\"color: #0000FF\">Qux</span><span> │</span>"); | ||||
|             result[3].ShouldBe("<span>├─────────────────┼───────┼─────┤</span>"); | ||||
|             result[4].ShouldBe("<span>│ </span><span style=\"text-decoration: underline\">Corgi</span><span>           │ </span><span style=\"font-weight: bold;font-style: italic;text-decoration: line-through\">Waldo</span><span> │ </span><span style=\"color: #7F7F7F\">Zap</span><span> │</span>"); | ||||
|             result[5].ShouldBe("<span>│ </span><span style=\"color: #FF0000\">╭─────────────╮</span><span> │       │     │</span>"); | ||||
|             result[6].ShouldBe("<span>│ </span><span style=\"color: #FF0000\">│</span><span> </span><span style=\"color: #0000FF\">Hello World</span><span> </span><span style=\"color: #FF0000\">│</span><span> │       │     │</span>"); | ||||
|             result[7].ShouldBe("<span>│ </span><span style=\"color: #FF0000\">╰─────────────╯</span><span> │       │     │</span>"); | ||||
|             result[8].ShouldBe("<span>└─────────────────┴───────┴─────┘</span>"); | ||||
|             result[9].ShouldBe("</pre>"); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										66
									
								
								src/Spectre.Console/AnsiConsole.Recording.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/Spectre.Console/AnsiConsole.Recording.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| using System; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A console capable of writing ANSI escape sequences. | ||||
|     /// </summary> | ||||
|     public static partial class AnsiConsole | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Starts recording the console output. | ||||
|         /// </summary> | ||||
|         public static void Record() | ||||
|         { | ||||
|             _recorder = new Recorder(_console.Value); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Exports all recorded console output as text. | ||||
|         /// </summary> | ||||
|         /// <returns>The recorded output as text.</returns> | ||||
|         public static string ExportText() | ||||
|         { | ||||
|             if (_recorder == null) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Cannot export text since a recording hasn't been started."); | ||||
|             } | ||||
|  | ||||
|             return _recorder.ExportText(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Exports all recorded console output as HTML. | ||||
|         /// </summary> | ||||
|         /// <returns>The recorded output as HTML.</returns> | ||||
|         public static string ExportHtml() | ||||
|         { | ||||
|             if (_recorder == null) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Cannot export HTML since a recording hasn't been started."); | ||||
|             } | ||||
|  | ||||
|             return _recorder.ExportHtml(); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Exports all recorded console output using a custom encoder. | ||||
|         /// </summary> | ||||
|         /// <param name="encoder">The encoder to use.</param> | ||||
|         /// <returns>The recorded output.</returns> | ||||
|         public static string ExportCustom(IAnsiConsoleEncoder encoder) | ||||
|         { | ||||
|             if (_recorder == null) | ||||
|             { | ||||
|                 throw new InvalidOperationException("Cannot export HTML since a recording hasn't been started."); | ||||
|             } | ||||
|  | ||||
|             if (encoder is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(encoder)); | ||||
|             } | ||||
|  | ||||
|             return _recorder.Export(encoder); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -21,10 +21,12 @@ namespace Spectre.Console | ||||
|             return console; | ||||
|         }); | ||||
|  | ||||
|         private static Recorder? _recorder; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the underlying <see cref="IAnsiConsole"/>. | ||||
|         /// </summary> | ||||
|         public static IAnsiConsole Console => _console.Value; | ||||
|         public static IAnsiConsole Console => _recorder ?? _console.Value; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the console's capabilities. | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| using System; | ||||
| using System.Diagnostics; | ||||
| using System.Globalization; | ||||
| using System.Security.Cryptography; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| @@ -60,6 +61,35 @@ namespace Spectre.Console | ||||
|             Number = null; | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Blends two colors. | ||||
|         /// </summary> | ||||
|         /// <param name="other">The other color.</param> | ||||
|         /// <param name="factor">The blend factor.</param> | ||||
|         /// <returns>The resulting color.</returns> | ||||
|         public Color Blend(Color other, float factor) | ||||
|         { | ||||
|             // https://github.com/willmcgugan/rich/blob/f092b1d04252e6f6812021c0f415dd1d7be6a16a/rich/color.py#L494 | ||||
|             return new Color( | ||||
|                 (byte)(R + ((other.R - R) * factor)), | ||||
|                 (byte)(G + ((other.G - G) * factor)), | ||||
|                 (byte)(B + ((other.B - B) * factor))); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Gets the hexadecimal representation of the color. | ||||
|         /// </summary> | ||||
|         /// <returns>The hexadecimal representation of the color.</returns> | ||||
|         public string ToHex() | ||||
|         { | ||||
|             return string.Format( | ||||
|                 CultureInfo.InvariantCulture, | ||||
|                 "{0}{1}{2}", | ||||
|                 R.ToString("X2", CultureInfo.InvariantCulture), | ||||
|                 G.ToString("X2", CultureInfo.InvariantCulture), | ||||
|                 B.ToString("X2", CultureInfo.InvariantCulture)); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public override int GetHashCode() | ||||
|         { | ||||
|   | ||||
| @@ -1,4 +1,5 @@ | ||||
| using System; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
| @@ -7,6 +8,32 @@ namespace Spectre.Console | ||||
|     /// </summary> | ||||
|     public static partial class AnsiConsoleExtensions | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Creates a recorder for the specified console. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console to record.</param> | ||||
|         /// <returns>A recorder for the specified console.</returns> | ||||
|         public static Recorder CreateRecorder(this IAnsiConsole console) | ||||
|         { | ||||
|             return new Recorder(console); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Writes the specified string value to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console to write to.</param> | ||||
|         /// <param name="text">The text to write.</param> | ||||
|         /// <param name="style">The text style.</param> | ||||
|         public static void Write(this IAnsiConsole console, string text, Style style) | ||||
|         { | ||||
|             if (console is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(console)); | ||||
|             } | ||||
|  | ||||
|             console.Write(new Segment(text, style)); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Writes an empty line to the console. | ||||
|         /// </summary> | ||||
| @@ -34,7 +61,7 @@ namespace Spectre.Console | ||||
|                 throw new ArgumentNullException(nameof(console)); | ||||
|             } | ||||
|  | ||||
|             console.Write(text, style); | ||||
|             console.Write(new Segment(text, style)); | ||||
|             console.WriteLine(); | ||||
|         } | ||||
|     } | ||||
|   | ||||
							
								
								
									
										44
									
								
								src/Spectre.Console/Extensions/RecorderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								src/Spectre.Console/Extensions/RecorderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,44 @@ | ||||
| using System; | ||||
| using Spectre.Console.Internal; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Contains extension methods for <see cref="Recorder"/>. | ||||
|     /// </summary> | ||||
|     public static class RecorderExtensions | ||||
|     { | ||||
|         private static readonly TextEncoder _textEncoder = new TextEncoder(); | ||||
|         private static readonly HtmlEncoder _htmlEncoder = new HtmlEncoder(); | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Exports the recorded content as text. | ||||
|         /// </summary> | ||||
|         /// <param name="recorder">The recorder.</param> | ||||
|         /// <returns>The recorded content as text.</returns> | ||||
|         public static string ExportText(this Recorder recorder) | ||||
|         { | ||||
|             if (recorder is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(recorder)); | ||||
|             } | ||||
|  | ||||
|             return recorder.Export(_textEncoder); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Exports the recorded content as HTML. | ||||
|         /// </summary> | ||||
|         /// <param name="recorder">The recorder.</param> | ||||
|         /// <returns>The recorded content as HTML.</returns> | ||||
|         public static string ExportHtml(this Recorder recorder) | ||||
|         { | ||||
|             if (recorder is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(recorder)); | ||||
|             } | ||||
|  | ||||
|             return recorder.Export(_htmlEncoder); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,4 +1,5 @@ | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
| @@ -30,8 +31,7 @@ namespace Spectre.Console | ||||
|         /// <summary> | ||||
|         /// Writes a string followed by a line terminator to the console. | ||||
|         /// </summary> | ||||
|         /// <param name="text">The string to write.</param> | ||||
|         /// <param name="style">The style to use.</param> | ||||
|         void Write(string text, Style style); | ||||
|         /// <param name="segment">The segment to write.</param> | ||||
|         void Write(Segment segment); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										19
									
								
								src/Spectre.Console/IAnsiConsoleEncoder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								src/Spectre.Console/IAnsiConsoleEncoder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| using System.Collections.Generic; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// Represents a console encoder that can encode | ||||
|     /// recorded segments into a string. | ||||
|     /// </summary> | ||||
|     public interface IAnsiConsoleEncoder | ||||
|     { | ||||
|         /// <summary> | ||||
|         /// Encodes the specified segments. | ||||
|         /// </summary> | ||||
|         /// <param name="segments">The segments to encode.</param> | ||||
|         /// <returns>The encoded string.</returns> | ||||
|         string Encode(IEnumerable<Segment> segments); | ||||
|     } | ||||
| } | ||||
| @@ -1,6 +1,7 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
| @@ -48,21 +49,14 @@ namespace Spectre.Console.Internal | ||||
|             _ansiBuilder = new AnsiBuilder(Capabilities, linkHasher); | ||||
|         } | ||||
|  | ||||
|         public void Write(string text, Style style) | ||||
|         public void Write(Segment segment) | ||||
|         { | ||||
|             if (string.IsNullOrEmpty(text)) | ||||
|             { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             style ??= Style.Plain; | ||||
|  | ||||
|             var parts = text.NormalizeLineEndings().Split(new[] { '\n' }); | ||||
|             var parts = segment.Text.NormalizeLineEndings().Split(new[] { '\n' }); | ||||
|             foreach (var (_, _, last, part) in parts.Enumerate()) | ||||
|             { | ||||
|                 if (!string.IsNullOrEmpty(part)) | ||||
|                 { | ||||
|                     _out.Write(_ansiBuilder.GetAnsi(part, style)); | ||||
|                     _out.Write(_ansiBuilder.GetAnsi(part, segment.Style)); | ||||
|                 } | ||||
|  | ||||
|                 if (!last) | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| using System; | ||||
| using System.IO; | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
| @@ -61,14 +62,14 @@ namespace Spectre.Console.Internal | ||||
|             Capabilities = capabilities; | ||||
|         } | ||||
|  | ||||
|         public void Write(string text, Style style) | ||||
|         public void Write(Segment segment) | ||||
|         { | ||||
|             if (_lastStyle?.Equals(style) != true) | ||||
|             if (_lastStyle?.Equals(segment.Style) != true) | ||||
|             { | ||||
|                 SetStyle(style); | ||||
|                 SetStyle(segment.Style); | ||||
|             } | ||||
|  | ||||
|             _out.Write(text.NormalizeLineEndings(native: true)); | ||||
|             _out.Write(segment.Text.NormalizeLineEndings(native: true)); | ||||
|         } | ||||
|  | ||||
|         private void SetStyle(Style style) | ||||
|   | ||||
							
								
								
									
										114
									
								
								src/Spectre.Console/Internal/HtmlEncoder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/Spectre.Console/Internal/HtmlEncoder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,114 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class HtmlEncoder : IAnsiConsoleEncoder | ||||
|     { | ||||
|         public string Encode(IEnumerable<Segment> segments) | ||||
|         { | ||||
|             var builder = new StringBuilder(); | ||||
|  | ||||
|             builder.Append("<pre style=\"font-size:90%;font-family:consolas,'Courier New',monospace\">\n"); | ||||
|  | ||||
|             foreach (var (_, first, _, segment) in segments.Enumerate()) | ||||
|             { | ||||
|                 if (segment.Text == "\n" && !first) | ||||
|                 { | ||||
|                     builder.Append('\n'); | ||||
|                     continue; | ||||
|                 } | ||||
|  | ||||
|                 var parts = segment.Text.Split(new[] { '\n' }, StringSplitOptions.None); | ||||
|                 foreach (var (_, _, last, line) in parts.Enumerate()) | ||||
|                 { | ||||
|                     if (string.IsNullOrEmpty(line)) | ||||
|                     { | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     builder.Append("<span"); | ||||
|                     if (!segment.Style.Equals(Style.Plain)) | ||||
|                     { | ||||
|                         builder.Append(" style=\""); | ||||
|                         builder.Append(BuildCss(segment.Style)); | ||||
|                         builder.Append('"'); | ||||
|                     } | ||||
|  | ||||
|                     builder.Append('>'); | ||||
|                     builder.Append(line); | ||||
|                     builder.Append("</span>"); | ||||
|  | ||||
|                     if (parts.Length > 1 && !last) | ||||
|                     { | ||||
|                         builder.Append('\n'); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|  | ||||
|             builder.Append("</pre>"); | ||||
|  | ||||
|             return builder.ToString().TrimEnd('\n'); | ||||
|         } | ||||
|  | ||||
|         private static string BuildCss(Style style) | ||||
|         { | ||||
|             var css = new List<string>(); | ||||
|  | ||||
|             var foreground = style.Foreground; | ||||
|             var background = style.Background; | ||||
|  | ||||
|             if ((style.Decoration & Decoration.Invert) != 0) | ||||
|             { | ||||
|                 var temp = foreground; | ||||
|                 foreground = background; | ||||
|                 background = temp; | ||||
|             } | ||||
|  | ||||
|             if ((style.Decoration & Decoration.Dim) != 0) | ||||
|             { | ||||
|                 var blender = background; | ||||
|                 if (blender.Equals(Color.Default)) | ||||
|                 { | ||||
|                     blender = Color.White; | ||||
|                 } | ||||
|  | ||||
|                 foreground = foreground.Blend(blender, 0.5f); | ||||
|             } | ||||
|  | ||||
|             if (!foreground.Equals(Color.Default)) | ||||
|             { | ||||
|                 css.Add($"color: #{foreground.ToHex()}"); | ||||
|             } | ||||
|  | ||||
|             if (!background.Equals(Color.Default)) | ||||
|             { | ||||
|                 css.Add($"background-color: #{background.ToHex()}"); | ||||
|             } | ||||
|  | ||||
|             if ((style.Decoration & Decoration.Bold) != 0) | ||||
|             { | ||||
|                 css.Add("font-weight: bold"); | ||||
|             } | ||||
|  | ||||
|             if ((style.Decoration & Decoration.Bold) != 0) | ||||
|             { | ||||
|                 css.Add("font-style: italic"); | ||||
|             } | ||||
|  | ||||
|             if ((style.Decoration & Decoration.Underline) != 0) | ||||
|             { | ||||
|                 css.Add("text-decoration: underline"); | ||||
|             } | ||||
|  | ||||
|             if ((style.Decoration & Decoration.Strikethrough) != 0) | ||||
|             { | ||||
|                 css.Add("text-decoration: line-through"); | ||||
|             } | ||||
|  | ||||
|             return string.Join(";", css); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										21
									
								
								src/Spectre.Console/Internal/TextEncoder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								src/Spectre.Console/Internal/TextEncoder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,21 @@ | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console.Internal | ||||
| { | ||||
|     internal sealed class TextEncoder : IAnsiConsoleEncoder | ||||
|     { | ||||
|         public string Encode(IEnumerable<Segment> segments) | ||||
|         { | ||||
|             var builder = new StringBuilder(); | ||||
|  | ||||
|             foreach (var segment in Segment.Merge(segments)) | ||||
|             { | ||||
|                 builder.Append(segment.Text); | ||||
|             } | ||||
|  | ||||
|             return builder.ToString().TrimEnd('\n'); | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										66
									
								
								src/Spectre.Console/Recorder.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										66
									
								
								src/Spectre.Console/Recorder.cs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,66 @@ | ||||
| using System; | ||||
| using System.Collections.Generic; | ||||
| using System.Text; | ||||
| using Spectre.Console.Rendering; | ||||
|  | ||||
| namespace Spectre.Console | ||||
| { | ||||
|     /// <summary> | ||||
|     /// A console recorder used to record output from a console. | ||||
|     /// </summary> | ||||
|     public sealed class Recorder : IAnsiConsole, IDisposable | ||||
|     { | ||||
|         private readonly IAnsiConsole _console; | ||||
|         private readonly List<Segment> _recorded; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public Capabilities Capabilities => _console.Capabilities; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public Encoding Encoding => _console.Encoding; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Width => _console.Width; | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public int Height => _console.Height; | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Initializes a new instance of the <see cref="Recorder"/> class. | ||||
|         /// </summary> | ||||
|         /// <param name="console">The console to record output for.</param> | ||||
|         public Recorder(IAnsiConsole console) | ||||
|         { | ||||
|             _console = console ?? throw new ArgumentNullException(nameof(console)); | ||||
|             _recorded = new List<Segment>(); | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Dispose() | ||||
|         { | ||||
|             // Only used for scoping. | ||||
|         } | ||||
|  | ||||
|         /// <inheritdoc/> | ||||
|         public void Write(Segment segment) | ||||
|         { | ||||
|             _recorded.Add(segment); | ||||
|             _console.Write(segment); | ||||
|         } | ||||
|  | ||||
|         /// <summary> | ||||
|         /// Exports the recorded data. | ||||
|         /// </summary> | ||||
|         /// <param name="encoder">The encoder.</param> | ||||
|         /// <returns>The recorded data represented as a string.</returns> | ||||
|         public string Export(IAnsiConsoleEncoder encoder) | ||||
|         { | ||||
|             if (encoder is null) | ||||
|             { | ||||
|                 throw new ArgumentNullException(nameof(encoder)); | ||||
|             } | ||||
|  | ||||
|             return encoder.Encode(_recorded); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -72,7 +72,7 @@ namespace Spectre.Console.Rendering | ||||
|             } | ||||
|  | ||||
|             Text = text.NormalizeLineEndings(); | ||||
|             Style = style; | ||||
|             Style = style ?? throw new ArgumentNullException(nameof(style)); | ||||
|             IsLineBreak = lineBreak; | ||||
|             IsWhiteSpace = string.IsNullOrWhiteSpace(text); | ||||
|         } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user