{"id":24693005,"url":"https://github.com/dusrdev/prettyconsole","last_synced_at":"2026-01-22T08:34:11.858Z","repository":{"id":45657976,"uuid":"415363123","full_name":"dusrdev/PrettyConsole","owner":"dusrdev","description":"High performance, feature rich and easy to use wrap over System.Console","archived":false,"fork":false,"pushed_at":"2024-11-14T12:58:12.000Z","size":225,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"stable","last_synced_at":"2025-01-20T02:39:37.934Z","etag":null,"topics":["cli","console","csharp","dotnet","shell"],"latest_commit_sha":null,"homepage":"","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/dusrdev.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-10-09T16:31:15.000Z","updated_at":"2024-11-14T12:55:43.000Z","dependencies_parsed_at":"2024-11-14T12:35:50.869Z","dependency_job_id":null,"html_url":"https://github.com/dusrdev/PrettyConsole","commit_stats":{"total_commits":80,"total_committers":2,"mean_commits":40.0,"dds":"0.21250000000000002","last_synced_commit":"4a05d7321f546079423b8502a0c83d121c790f13"},"previous_names":[],"tags_count":9,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dusrdev%2FPrettyConsole","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dusrdev%2FPrettyConsole/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dusrdev%2FPrettyConsole/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/dusrdev%2FPrettyConsole/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/dusrdev","download_url":"https://codeload.github.com/dusrdev/PrettyConsole/tar.gz/refs/heads/stable","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":235756994,"owners_count":19040482,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["cli","console","csharp","dotnet","shell"],"created_at":"2025-01-26T20:18:13.287Z","updated_at":"2026-01-22T08:34:11.839Z","avatar_url":"https://github.com/dusrdev.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# PrettyConsole\n\n[![NuGet](https://img.shields.io/nuget/v/PrettyConsole.svg?style=flat-square)](https://www.nuget.org/packages/PrettyConsole)\n[![NuGet Downloads](https://img.shields.io/nuget/dt/PrettyConsole?style=flat\u0026label=Downloads)](https://www.nuget.org/packages/PrettyConsole)\n[![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg?style=flat-square)](License.txt)\n[![.NET](https://img.shields.io/badge/.NET-10.0-512BD4?style=flat-square)](https://dotnet.microsoft.com/en-us/download/dotnet/10.0)\n\nPrettyConsole is a high-performance, ultra-low-latency, allocation-free extension layer over `System.Console`. The library uses C# extension members (`extension(Console)`) so every API lights up directly on `System.Console` once `using PrettyConsole;` is in scope. It is trimming/AOT ready, preserves SourceLink metadata, and keeps the familiar console experience while adding structured rendering, menus, progress bars, and advanced input helpers.\n\n## Features\n\n- 🚀 Zero-allocation interpolated string handler (`PrettyConsoleInterpolatedStringHandler`) for inline colors and formatting\n- 🎨 Inline color composition with `ConsoleColor` tuples and helpers (`DefaultForeground`, `DefaultBackground`, `Default`) plus `AnsiColors` utilities when you need raw ANSI sequences\n- 🔁 Advanced rendering primitives (`Overwrite`, `ClearNextLines`, `GoToLine`, `SkipLines`, progress bars) that respect console pipes\n- 🧱 Handler-aware `WhiteSpace` struct for zero-allocation padding directly inside interpolated strings\n- 🧰 Rich input helpers (`TryReadLine`, `Confirm`, `RequestAnyInput`) with `IParsable\u003cT\u003e` and enum support\n- ⚙️ Allocation-conscious span-first APIs (`ISpanFormattable`, `ReadOnlySpan\u003cchar\u003e`, `Console.WriteWhiteSpaces` / `TextWriter.WriteWhiteSpaces`)\n- ⛓ Output routing through `OutputPipe.Out` and `OutputPipe.Error` so piping/redirects continue to work\n\n## Performance\n\nBenchmarkDotNet measures [styled output performance](Benchmarks/BenchmarkDotNet.Artifacts/results/Benchmarks.StyledOutputBenchmarks-report-github.md) for a single line write:\n\n| Method         | Mean        | Ratio         | Gen0   | Allocated | Alloc Ratio   |\n|--------------- |------------:|--------------:|-------:|----------:|--------------:|\n| PrettyConsole  |    55.96 ns | 90.23x faster |      - |         - |            NA |\n| SpectreConsole | 5,046.29 ns |      baseline | 2.1193 |   17840 B |               |\n| SystemConsole  |    71.64 ns | 70.44x faster | 0.0022 |      24 B | 743.333x less |\n\nPrettyConsole is **the go-to choice for ultra-low-latency, allocation-free console rendering**, running 90X faster than **Spectre.Console** while allocating nothing and even beating the manual unrolling with the BCL.\n\n## Installation\n\n```bash\ndotnet add package PrettyConsole\n```\n\n## Examples\n\nStandalone samples made with .NET 10 file-based apps with preview clips are available in [Examples](Examples/README.md).\n\n## Usage\n\n### Bring PrettyConsole APIs into scope\n\n```csharp\nusing PrettyConsole;          // Extension members + OutputPipe\nusing static System.Console;  // Optional for terser call sites\n```\n\nThis setup lets you call `Console.WriteInterpolated`, `Console.Overwrite`, `Console.TryReadLine`, etc. The original `System.Console` APIs remain available—call `System.Console.ReadKey()` or `System.Console.SetCursorPosition()` directly whenever you need something the extensions do not provide.\n\n### Interpolated strings \u0026 inline colors\n\n`PrettyConsoleInterpolatedStringHandler` now buffers interpolated content in a pooled buffer before flushing to the selected pipe. Colors auto-reset at the end of each call. `Console.WriteInterpolated` and `Console.WriteLineInterpolated` return the number of visible characters written (handler-emitted escape sequences are excluded) so you can drive padding/width calculations from the same call sites.\n\n```csharp\nConsole.WriteInterpolated($\"Hello {ConsoleColor.Green / ConsoleColor.DefaultBackground}world{ConsoleColor.Default}!\");\nConsole.WriteInterpolated(OutputPipe.Error, $\"{ConsoleColor.Yellow / ConsoleColor.DefaultBackground}warning{ConsoleColor.Default}: {message}\");\n\nif (!Console.TryReadLine(out int choice, $\"Pick option {ConsoleColor.Cyan / ConsoleColor.DefaultBackground}1-5{ConsoleColor.Default}: \")) {\n    Console.WriteLineInterpolated($\"{ConsoleColor.Red / ConsoleColor.DefaultBackground}Not a number.{ConsoleColor.Default}\");\n}\n\n// Zero-allocation padding directly from the handler\nConsole.WriteInterpolated($\"Header{new WhiteSpace(6)}Value\");\n```\n\n`ConsoleColor.DefaultForeground`, `ConsoleColor.DefaultBackground`, and the `/` operator overload make it easy to compose foreground/background tuples inline (`ConsoleColor.Red / ConsoleColor.White`).\n\n#### Inline decorations via `Markup`\n\nWhen ANSI escape sequences are safe to emit (`Console.IsOutputRedirected`/`IsErrorRedirected` are both `false`), the `Markup` helper exposes ready-to-use toggles for underline, bold, italic, and strikethrough:\n\n```csharp\nConsole.WriteLineInterpolated($\"{Markup.Bold}Build{Markup.ResetBold} {Markup.Underline}completed{Markup.ResetUnderline} in {elapsed:duration}\"); // e.g. \"completed in 2h 3m 17s\"\n```\n\nAll fields collapse to `string.Empty` when markup is disabled, so the same call sites continue to work when output is redirected or the terminal ignores decorations. Use `Markup.Reset` if you want to reset every decoration at once.\n\n#### Formatting \u0026 alignment helpers\n\n- **`TimeSpan :duration` format** — the interpolated string handler understands the custom `:duration` specifier. It emits integer `hours`/`minutes`/`seconds` tokens (e.g., `5h 32m 12s`, `27h 12m 3s`, `123h 0m 0s`) without allocations, and the hour component keeps growing past 24 so long-running tasks stay accurate. Minutes/seconds are not zero-padded so the output stays compact:\n\n  ```csharp\n  var elapsed = stopwatch.Elapsed;\n  Console.WriteInterpolated($\"Completed in {elapsed:duration}\"); // Completed in 12h 5m 33s\n  ```\n\n- **`double :bytes` format** — pass any `double` (cast integral sizes if needed) with the `:bytes` specifier to render human-friendly binary size units. Values scale by powers of 1024 through `B`, `KB`, `MB`, `GB`, `TB`, `PB`, and use the `#,##0.##` format so thousands separators and up to two decimal digits follow the current culture:\n\n  ```csharp\n  var transferred = 12_884_901d;\n  Console.WriteInterpolated($\"Uploaded {transferred:bytes}\"); // Uploaded 12.3 MB\n  Console.WriteInterpolated($\"Remaining {remaining,8:bytes}\"); // right-aligned units stay tidy\n  ```\n\n- **Alignment** — standard alignment syntax works the same way it does with regular interpolated strings, but the handler writes directly into the console buffer. This keeps columnar output zero-allocation friendly:\n\n  ```csharp\n  Console.WriteInterpolated($\"|{\"Label\",-10}|{value,10:0.00}|\");\n  ```\n\nYou can combine both, e.g., `$\"{elapsed,8:duration}\"`, to keep progress/status displays tidy.\n\n- **`WhiteSpace` struct for padding** — pass `new WhiteSpace(length)` inside an interpolated string to emit that many spaces straight from the handler without allocating intermediate strings.\n\n- **Custom escape sequences** — if you need your own ANSI code (extra markup/colors), keep it in an interpolated hole instead of hardcoding it into the literal so the handler can treat it like other escape spans:\n\n```csharp\nvar rose = \"\\u001b[38;5;213m\"; // custom 256-color escape\nConsole.WriteInterpolated($\"{rose}accent text{Markup.Reset}\");\n```\n\nAvoid embedding the escape directly in the literal (`\"\\u001b[38;5;213maccent text\"`), which would be measured as visible width and could skew padding/alignment.\n\n### Basic outputs\n\n```csharp\n// Interpolated text\nConsole.WriteInterpolated($\"Processed {items} items in {elapsed:duration}\"); // Processed 42 items in 3h 44m 9s\nConsole.WriteLineInterpolated(OutputPipe.Error, $\"{ConsoleColor.Magenta}debug{ConsoleColor.Default}\");\n\n// Span + color overloads (no boxing)\nReadOnlySpan\u003cchar\u003e header = \"Title\";\nConsole.Write(header, OutputPipe.Out, ConsoleColor.White, ConsoleColor.DarkBlue);\nConsole.NewLine(); // writes newline to the default output pipe\n\n// ISpanFormattable (works with ref structs)\nConsole.Write(percentage, OutputPipe.Out, ConsoleColor.Cyan, ConsoleColor.DefaultBackground, format: \"F2\", formatProvider: null);\n```\n\nBehind the scenes these overloads rent buffers from the shared `ArrayPool\u003cchar\u003e` and route output to the correct pipe through `ConsoleContext.GetWriter`.\n\n### Basic inputs\n\n```csharp\nif (!Console.TryReadLine(out int port, $\"Port ({ConsoleColor.Green}5000{ConsoleColor.Default}): \")) {\n    port = 5000;\n}\n\n// `TryReadLine\u003cTEnum\u003e` and `TryReadLine` with defaults\nif (!Console.TryReadLine(out DayOfWeek day, ignoreCase: true, $\"Day? \")) {\n    day = DayOfWeek.Monday;\n}\n\nvar apiKey = Console.ReadLine($\"Enter API key ({ConsoleColor.DarkGray}optional{ConsoleColor.Default}): \");\n```\n\nAll input helpers work with `IParsable\u003cT\u003e` and enums, respect the active culture, and honor `OutputPipe` when prompts are colored.\n\n### Advanced inputs\n\n```csharp\nConsole.RequestAnyInput($\"Press {ConsoleColor.Yellow}any key{ConsoleColor.Default} to continue…\");\n\nif (!Console.Confirm($\"Deploy to production? ({ConsoleColor.Green}y{ConsoleColor.Default}/{ConsoleColor.Red}n{ConsoleColor.Default}) \")) {\n    return;\n}\n\nvar customTruths = new[] { \"sure\", \"do it\" };\nbool overwrite = Console.Confirm(customTruths, $\"Overwrite existing files? \", emptyIsTrue: false);\n```\n\n### Rendering helpers\n\n```csharp\nConsole.ClearNextLines(3, OutputPipe.Error);\nint line = Console.GetCurrentLine();\n// … draw something …\nConsole.GoToLine(line);\nConsole.SetColors(ConsoleColor.White, ConsoleColor.DarkBlue);\nConsole.ResetColors();\nConsole.SkipLines(2); // keep multi-line UIs (progress bars, dashboards) and continue writing below them\n```\n\n`ConsoleContext.Out`/`Error` expose the live writers (both are settable if you need to swap in test doubles). Use `Console.WriteWhiteSpaces(int length, OutputPipe pipe)` for convenient padding from call sites, or call `WriteWhiteSpaces(int)` on an existing writer. `Console.SkipLines(n)` advances the cursor without clearing so you can keep overwritten UI (progress bars, spinners, dashboards) visible after completion:\n\n```csharp\nConsole.WriteWhiteSpaces(8, OutputPipe.Error); // pad status blocks\nConsoleContext.Error.WriteWhiteSpaces(4);      // same via writer\n```\n\n### Advanced outputs\n\n```csharp\nConsole.Overwrite(() =\u003e {\n    Console.WriteLineInterpolated(OutputPipe.Error, $\"{ConsoleColor.Cyan}Working…{ConsoleColor.Default}\");\n    Console.WriteInterpolated(OutputPipe.Error, $\"{ConsoleColor.DarkGray}Elapsed:{ConsoleColor.Default} {stopwatch.Elapsed:duration}\"); // Elapsed: 0h 1m 12s\n}, lines: 2);\n\n// Prevent closure allocations with state + generic overload\nConsole.Overwrite((left, right), tuple =\u003e {\n    Console.WriteInterpolated($\"{tuple.left} ←→ {tuple.right}\");\n}, lines: 1);\n\nawait Console.TypeWrite(\"Booting systems…\", (ConsoleColor.Green, ConsoleColor.Black));\nawait Console.TypeWriteLine(\"Ready.\", ConsoleColor.Default);\n```\n\nAlways call `Console.ClearNextLines(totalLines, pipe)` once after the last `Overwrite` to erase the region when you are done.\n\n### Menus and tables\n\n```csharp\nvar choice = Console.Selection(\"Pick an environment:\", new[] { \"Dev\", \"QA\", \"Prod\" });\nvar multi = Console.MultiSelection(\"Services to restart:\", new[] { \"API\", \"Worker\", \"Scheduler\" });\nvar (area, action) = Console.TreeMenu(\"Actions\", new Dictionary\u003cstring, IList\u003cstring\u003e\u003e {\n    [\"Users\"] = new[] { \"List\", \"Create\", \"Disable\" },\n    [\"Jobs\"] = new[] { \"Queue\", \"Retry\" }\n});\n\nConsole.Table(\n    headers: new[] { \"Name\", \"Status\" },\n    columns: new[] {\n        new[] { \"API\", \"Worker\" },\n        new[] { \"Running\", \"Stopped\" }\n    }\n);\n```\n\nMenus validate user input (throwing `ArgumentException` on invalid selections) and use the padding helpers internally to keep columns aligned.\n\n### Progress bars\n\n```csharp\nusing var progress = new ProgressBar {\n    ProgressChar = '■',\n    ForegroundColor = ConsoleColor.DarkGray,\n    ProgressColor = ConsoleColor.Green,\n};\n\nfor (int i = 0; i \u003c= 100; i += 5) {\n    progress.Update(i, $\"Downloading chunk {i / 5}\");\n    await Task.Delay(50);\n}\n\n// Need separate status + bar lines? sameLine: false\nprogress.Update(42.5, \"Syncing\", sameLine: false);\n\n// One-off render without state\nProgressBar.Render(OutputPipe.Error, 75, ConsoleColor.Magenta, '*', maxLineWidth: 32);\n```\n\n`ProgressBar.Update` always re-renders (even if the percentage didn't change) so you can refresh status text. You can also set `ProgressBar.MaxLineWidth` on the instance to limit the rendered `[=====]  42%` line width before each update, mirroring the `maxLineWidth` option on `ProgressBar.Render`. The helper `ProgressBar.Render` keeps the cursor on the same line, which is ideal inside `Console.Overwrite`, and accepts an optional `maxLineWidth` so the entire `[=====]  42%` line can be constrained for left-column layouts. For dynamic headers, use the overload that accepts a `PrettyConsoleInterpolatedStringHandlerFactory`, mirroring the spinner pattern.\n\n#### Spinner (indeterminate progress)\n\n`Spinner` renders animated frames on the error pipe. `PrettyConsoleInterpolatedStringHandlerFactory` overloads take a lambda that creates a `PrettyConsoleInterpolatedStringHandler` via the builder for per-frame headers:\n\n```csharp\nvar spinner = new Spinner();\nawait spinner.RunAsync(workTask, (builder, out handler) =\u003e\n    handler = builder.Build(OutputPipe.Error, $\"Syncing {DateTime.Now:T}\"));\n```\n\nThe factory runs each frame so you can inject dynamic status text without allocations while avoiding extra struct copies.\n\n#### Multiple progress bars with tasks + channels\n\n```csharp\nusing System.Linq;\nusing System.Threading.Channels;\n\nvar downloads = new[] { \"Video.mp4\", \"Archive.zip\", \"Assets.pak\" };\nvar progress = new double[downloads.Length];\nvar updates = Channel.CreateUnbounded\u003c(int index, double percent)\u003e();\n\n// Producers push progress updates\nvar producers = downloads\n    .Select((name, index) =\u003e Task.Run(async () =\u003e {\n        for (int p = 0; p \u003c= 100; p += Random.Shared.Next(5, 15)) {\n            await updates.Writer.WriteAsync((index, p));\n            await Task.Delay(Random.Shared.Next(40, 120));\n        }\n    }))\n    .ToArray();\n\n// Consumer renders stacked bars each time an update arrives\nvar consumer = Task.Run(async () =\u003e {\n    await foreach (var (index, percent) in updates.Reader.ReadAllAsync()) {\n        progress[index] = percent;\n\n        Console.Overwrite(progress, state =\u003e {\n            for (int i = 0; i \u003c state.Length; i++) {\n                Console.WriteInterpolated(OutputPipe.Error, $\"Task {i + 1} ({downloads[i]}): \");\n                ProgressBar.Render(OutputPipe.Error, state[i], ConsoleColor.Cyan);\n            }\n        }, lines: downloads.Length, pipe: OutputPipe.Error);\n    }\n});\n\nawait Task.WhenAll(producers);\nupdates.Writer.Complete();\nawait consumer;\n\nConsole.ClearNextLines(downloads.Length, OutputPipe.Error); // ensure no artifacts remain\n```\n\nEach producer reports progress over the channel, the consumer loops with `ReadAllAsync`, and `Console.Overwrite` redraws the stacked bars on every update. After the consumer completes, clear the region once to remove the progress UI.\n\n### Pipes \u0026 writers\n\nPrettyConsole keeps the original console streams accessible (and settable for tests) via `ConsoleContext`:\n\n```csharp\nTextWriter @out = ConsoleContext.Out;\nTextWriter @err = ConsoleContext.Error;\nTextReader @in = ConsoleContext.In;\n```\n\nUse these when you need direct writer access (custom buffering, `WriteWhiteSpaces`, etc.) or swap in mocks for testing. In cases where you must call raw `System.Console` APIs (e.g., `Console.ReadKey(true)`), do so explicitly—PrettyConsole never hides the built-in console.\n\n## Contributing\n\nContributions are welcome! Fork the repo, create a branch, and open a pull request. Bug reports and feature requests are tracked through GitHub issues.\n\n## Contact\n\nFor bug reports, feature requests, or sponsorship inquiries reach out at \u003cdusrdev@gmail.com\u003e.\n\n\u003e This project is proudly made in Israel 🇮🇱 for the benefit of mankind.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdusrdev%2Fprettyconsole","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdusrdev%2Fprettyconsole","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdusrdev%2Fprettyconsole/lists"}