|
3 | 3 | [](https://github.com/jonathanvdc/Pixie/actions/workflows/ci.yml) |
4 | 4 | [](https://www.nuget.org/packages/Pixie) |
5 | 5 |
|
6 | | -Pixie is a C# library that prints beautifully formatted output to the console. You describe your layout using a high-level API and Pixie turns it into neatly-formatted text. |
| 6 | +Pixie is a C# library for building polished command-line applications. It gives you a high-level API for terminal output, diagnostics, help text, and GNU-style option parsing, then renders the best output your terminal can support. |
7 | 7 |
|
8 | | -Essentially, Pixie is all about creating gorgeous console applications with minimal effort. |
| 8 | +Pixie is a good fit when you want to: |
9 | 9 |
|
10 | | -Key features: |
| 10 | +* print structured, readable console output instead of hand-formatting strings, |
| 11 | +* show rich diagnostics with source context and caret highlights, |
| 12 | +* generate help output from the same option definitions you parse, and |
| 13 | +* keep CLI output readable across terminals with different Unicode and styling support. |
11 | 14 |
|
12 | | - * **Caret diagnostics.** Pixie has built-in support for caret diagnostics. Want to point out an error in source code? Pixie's really good at that. It highlights the error and colors both the highlighted text and the squiggle beneath it. Pixie also prints line numbers and even throws in a couple of lines of context. |
| 15 | +## Quick start |
13 | 16 |
|
14 | | -  |
| 17 | +Install Pixie: |
15 | 18 |
|
16 | | - * **Argument parsing.** In addition to formatting application output, Pixie can also parse command-line arguments. |
| 19 | +```sh |
| 20 | +dotnet add package Pixie |
| 21 | +``` |
17 | 22 |
|
18 | | - When parsing command-line arguments, Pixie tries to go the extra mile and guide users toward correct program usage by spellchecking options and proactively printing option usage. Here's what that looks like in action. |
| 23 | +Then write and log a message: |
19 | 24 |
|
20 | | -  |
| 25 | +```cs |
| 26 | +using Pixie; |
| 27 | +using Pixie.Terminal; |
21 | 28 |
|
22 | | - * **Help messages.** Help messages are wonderful for users but they're kind of tedious to write and maintain. Pixie generates them automatically from the same data that is used to parse command-line arguments. |
23 | | - |
24 | | - Here's an excerpt from an example help message. The formatting, grouping and fancy Unicode characters are all automagically generated by Pixie. |
| 29 | +var log = TerminalLog.Acquire(); |
25 | 30 |
|
26 | | -  |
| 31 | +log.Log(new LogEntry( |
| 32 | + Severity.Info, |
| 33 | + "Hello from Pixie.")); |
| 34 | +``` |
27 | 35 |
|
28 | | - * **Graceful degradation.** Pixie tries to make its output as pretty as your terminal will allow and degrades its output gracefully on terminal implementations that don't support all of Unicode. |
29 | | - |
30 | | - For example, when rendered on `xterm` (a Unix terminal), this bulleted list item uses Unicode characters and ANSI control sequences: |
| 36 | +`TerminalLog.Acquire()` is the usual entry point for application output. In most applications, you should acquire a log once and reuse it. |
31 | 37 |
|
32 | | -  |
| 38 | +## Packages |
33 | 39 |
|
34 | | - The default Windows console doesn't support those features, so Pixie uses ASCII characters and the `System.Console` API there: |
| 40 | +Pixie is split into a small set of packages and assemblies: |
35 | 41 |
|
36 | | -  |
| 42 | +| Package | Purpose | |
| 43 | +| --- | --- | |
| 44 | +| [`Pixie`](https://www.nuget.org/packages/Pixie) | Core logging, markup, diagnostics, option parsing, and terminal integration. | |
| 45 | +| `Pixie.Terminal` | Terminal rendering types. This assembly ships with the `Pixie` package, so you usually do not install it separately. | |
| 46 | +| [`Pixie.Loyc`](https://www.nuget.org/packages/Pixie.Loyc) | Optional Loyc interoperability for translating Loyc diagnostics into Pixie output. | |
37 | 47 |
|
38 | | - * **Pretty output.** Pixie makes a real effort to produce good-looking output. It supports aligning and word-wrapping text. It also has built-in support for common types of messages, like diagnostics (errors, warnings, etc.) and help messages. |
| 48 | +Install Loyc support only if you need it: |
39 | 49 |
|
40 | | - To see why this is something you might want, just take a look at the formatting of `mcs`'s error message below. Note in particular how the word "suitable" is split up awkwardly. |
| 50 | +```sh |
| 51 | +dotnet add package Pixie.Loyc |
| 52 | +``` |
41 | 53 |
|
42 | | -  |
| 54 | +## What Pixie can do |
43 | 55 |
|
44 | | - Here's what that error message would have looked like if `mcs` used Pixie. Much better, right? |
| 56 | +### Caret diagnostics |
45 | 57 |
|
46 | | -  |
| 58 | +Pixie has built-in support for caret diagnostics. It can highlight a source region, emphasize the most relevant span, and render line numbers with surrounding context. |
47 | 59 |
|
48 | | - * **Loyc interop.** Pixie can gracefully translate and log diagnostics produced by [Loyc](https://github.com/qwertie/ecsharp) libraries (including the EC# parser and LeMP). The optional Loyc interop logic is bundled in the `Pixie.Loyc` package, which can be installed in addition to the regular `Pixie` package. |
| 60 | + |
49 | 61 |
|
50 | | - Here's what a diagnostic produced by Loyc looks like after Pixie has translated it: |
| 62 | +### Argument parsing with feedback |
51 | 63 |
|
52 | | -  |
| 64 | +Pixie can parse GNU-style command-line options and report mistakes in a user-friendly way, including usage guidance and option name suggestions. |
53 | 65 |
|
54 | | - * **Customization.** Pixie is customizable: you can easily configure the existing renderers and define your own markup elements and renderers. |
| 66 | + |
| 67 | + |
| 68 | +### Help messages |
| 69 | + |
| 70 | +Pixie can generate help output from the same option definitions you use for parsing, so parsing and documentation stay in sync. |
| 71 | + |
| 72 | + |
| 73 | + |
| 74 | +### Graceful terminal degradation |
| 75 | + |
| 76 | +Pixie tries to produce the nicest output your terminal can handle. When Unicode characters or ANSI styling are unavailable, it falls back to simpler representations instead of breaking formatting. |
| 77 | + |
| 78 | +Unicode-rich rendering: |
| 79 | + |
| 80 | + |
| 81 | + |
| 82 | +Simpler fallback rendering: |
| 83 | + |
| 84 | + |
| 85 | + |
| 86 | +### Readable layout |
| 87 | + |
| 88 | +Pixie supports alignment, wrapping, and reusable markup nodes for common CLI output patterns. |
| 89 | + |
| 90 | +Without careful layout, terminal messages can become awkward: |
| 91 | + |
| 92 | + |
| 93 | + |
| 94 | +With Pixie, the same message can be wrapped and structured more cleanly: |
| 95 | + |
| 96 | + |
| 97 | + |
| 98 | +### Loyc interoperability |
| 99 | + |
| 100 | +If you use Loyc libraries such as EC# or LeMP, `Pixie.Loyc` can translate their diagnostics into Pixie markup so they render consistently with the rest of your application output. |
| 101 | + |
| 102 | + |
55 | 103 |
|
56 | 104 | ## Getting started |
57 | 105 |
|
58 | | -To use Pixie in one of your projects, simply install the [Pixie NuGet package](https://www.nuget.org/packages/Pixie) and start coding. Here are some basic first steps: |
| 106 | +### 1. Acquire a log |
| 107 | + |
| 108 | +Pixie's main output abstraction is `ILog`. Logs accept `LogEntry` values, which are self-contained messages with a severity and a markup tree. |
| 109 | + |
| 110 | +For terminal applications, `TerminalLog.Acquire()` is the usual choice: |
| 111 | + |
| 112 | +```cs |
| 113 | +using Pixie.Terminal; |
| 114 | + |
| 115 | +var log = TerminalLog.Acquire(); |
| 116 | +``` |
| 117 | + |
| 118 | +### 2. Log a message |
| 119 | + |
| 120 | +A `LogEntry` consists of a `Severity` and a `MarkupNode`. Plain strings can be used directly as markup, so the smallest useful example is: |
| 121 | + |
| 122 | +```cs |
| 123 | +using Pixie; |
| 124 | +using Pixie.Terminal; |
| 125 | + |
| 126 | +var log = TerminalLog.Acquire(); |
59 | 127 |
|
60 | | - * **Acquiring a log.** Your application will probably want to send output to the terminal. Pixie's preferred abstraction for doing that is the `ILog`. All logs define a `void Log(LogEntry)` method, which sends a single, self-contained message to the log. |
| 128 | +log.Log(new LogEntry( |
| 129 | + Severity.Error, |
| 130 | + "Something went wrong.")); |
| 131 | +``` |
61 | 132 |
|
62 | | - There are many log implementations, but you probably want a log that sends messages straight to the terminal. You can acquire one of those like so: |
| 133 | +If you want explicit markup nodes, use types from `Pixie.Markup`, such as `Text`, `Sequence`, `BulletedList`, or `HighlightedSource`. |
63 | 134 |
|
64 | | - ```cs |
65 | | - using Pixie.Terminal; |
66 | | - // ... |
67 | | - var log = TerminalLog.Acquire(); |
68 | | - ``` |
| 135 | +### 3. Parse command-line arguments |
69 | 136 |
|
70 | | - > **Pro tip:** acquiring a log is something you want to do only once, for concurrency and performance reasons. |
| 137 | +Pixie includes GNU-style option parsing. You define options once, then parse arguments and read back typed values. |
71 | 138 |
|
72 | | - * **Logging messages.** As stated before, a `LogEntry` is a self-contained message that can be sent to any log. It consists of a `Severity` (which can be either `Error`, `Warning`, `Message` or `Info`) and a `MarkupNode`. The latter is essentially a description of what you want the user to see. |
| 139 | +```cs |
| 140 | +using Pixie; |
| 141 | +using Pixie.Options; |
| 142 | +using Pixie.Terminal; |
73 | 143 |
|
74 | | - A `MarkupNode` implementation can be anything ranging from a lowly text message to a full-blown caret diagnostic. In this example, we'll just log a text message. Those can be created by calling `new Text("message")` or by implicitly converting a `string` to a `MarkupNode`. |
| 144 | +var log = TerminalLog.Acquire(); |
75 | 145 |
|
76 | | - Putting it all together: |
| 146 | +var helpFlag = FlagOption.CreateFlagOption( |
| 147 | + OptionForm.Short("h"), |
| 148 | + OptionForm.Long("help")); |
77 | 149 |
|
78 | | - ```cs |
79 | | - using Pixie; |
80 | | - using Pixie.Markup; |
81 | | - // ... |
82 | | - log.Log( |
83 | | - new LogEntry( |
84 | | - Severity.Error, |
85 | | - new Text("Something went horribly wrong."))); |
86 | | - ``` |
| 150 | +var filesOption = SequenceOption.CreateStringOption( |
| 151 | + OptionForm.Long("files")); |
87 | 152 |
|
88 | | - * **Parsing command-line arguments.** Pixie's approach to parsing command-line arguments is pretty standard. You define a set of options and then use those to parse command-line arguments. |
| 153 | +var parser = new GnuOptionSetParser( |
| 154 | + new Option[] { helpFlag }, |
| 155 | + filesOption); |
89 | 156 |
|
90 | | - ```cs |
91 | | - using Pixie.Options; |
92 | | - // ... |
93 | | - // Define a --help/-h flag option. |
94 | | - var helpFlag = FlagOption.CreateFlagOption( |
95 | | - OptionForm.Short("h"), |
96 | | - OptionForm.Long("help")); |
| 157 | +var parsedArgs = parser.Parse(new[] { "input.cs", "-h" }, log); |
97 | 158 |
|
98 | | - // Define a pseudo-option for positional arguments. |
99 | | - var filesOption = SequenceOption.CreateStringOption( |
100 | | - OptionForm.Long("files")); |
| 159 | +bool showHelp = parsedArgs.GetValue<bool>(helpFlag); |
| 160 | +string[] files = parsedArgs.GetValue<string[]>(filesOption); |
| 161 | +``` |
101 | 162 |
|
102 | | - // Create a parser for GNU-style command-line arguments. |
103 | | - var parser = new GnuOptionSetParser( |
104 | | - new Option[] { helpFlag }, // <-- options |
105 | | - filesOption); // <-- pseudo-option for positional arguments |
| 163 | +When parsing fails, Pixie can report errors to the log instead of leaving formatting and recovery entirely up to the application. |
106 | 164 |
|
107 | | - // Parse command-line arguments. The parser automatically |
108 | | - // recovers from errors and sends error messages to `log`. |
109 | | - var parsedArgs = parser.Parse(new[] { "hi", "-h" }, log); |
| 165 | +### 4. Document options and generate help |
110 | 166 |
|
111 | | - // Recover parsed arguments. |
112 | | - bool showHelp = parsedArgs.GetValue<bool>(helpFlag); |
113 | | - string[] files = parsedArgs.GetValue<string[]>(filesOption); |
114 | | - ``` |
| 167 | +Options can carry categories, descriptions, and parameter metadata. That same information can then be used to generate help output. |
115 | 168 |
|
116 | | - * **Documenting options.** Adding documentation to your options is easy. Just call `WithCategory`, `WithDescription` and/or `WithParameters` on the options you'd like to document. |
| 169 | +```cs |
| 170 | +using Pixie.Markup; |
| 171 | +using Pixie.Options; |
117 | 172 |
|
118 | | - For example, here's how to document the `-x` option from `gcc` and `clang`. |
| 173 | +var xOption = SequenceOption.CreateStringOption(OptionForm.Short("x")) |
| 174 | + .WithCategory("Input and output") |
| 175 | + .WithDescription( |
| 176 | + "Specify explicitly the language for the following input files.") |
| 177 | + .WithParameters( |
| 178 | + new SymbolicOptionParameter("language"), |
| 179 | + new SymbolicOptionParameter("file", true)); |
119 | 180 |
|
120 | | - ```cs |
121 | | - xOption = SequenceOption.CreateStringOption(OptionForm.Short("x")) |
122 | | - .WithCategory("Input and output") |
123 | | - .WithDescription( |
124 | | - "Specify explicitly the language for the " + |
125 | | - "following input files.") |
126 | | - .WithParameters( |
127 | | - new SymbolicOptionParameter("language"), // <-- not varargs |
128 | | - new SymbolicOptionParameter("file", true)); // <-- varargs |
129 | | - ``` |
| 181 | +var help = new HelpMessage( |
| 182 | + "An example application built with Pixie.", |
| 183 | + "example [files-or-options]", |
| 184 | + new Option[] { xOption }); |
| 185 | +``` |
| 186 | + |
| 187 | +To see a more complete example, check [Examples/PrintHelp/Program.cs](Examples/PrintHelp/Program.cs). |
| 188 | + |
| 189 | +### 5. Render rich diagnostics |
| 190 | + |
| 191 | +Pixie's diagnostic model is especially useful when you already know the source span you want to highlight. |
| 192 | + |
| 193 | +```cs |
| 194 | +using Pixie; |
| 195 | +using Pixie.Code; |
| 196 | +using Pixie.Markup; |
| 197 | +using Pixie.Terminal; |
| 198 | + |
| 199 | +var log = TerminalLog.Acquire(); |
| 200 | + |
| 201 | +const string source = "public static class Program\n{\n public Program()\n { }\n}"; |
| 202 | +var document = new StringDocument("code.cs", source); |
| 203 | +var nameOffset = source.IndexOf("Program()"); |
| 204 | + |
| 205 | +var focusRegion = new SourceRegion( |
| 206 | + new SourceSpan(document, nameOffset, "Program".Length)); |
| 207 | + |
| 208 | +log.Log(new LogEntry( |
| 209 | + Severity.Error, |
| 210 | + new HighlightedSource(focusRegion, focusRegion))); |
| 211 | +``` |
| 212 | + |
| 213 | +For a fuller version with transforms and custom renderer configuration, see [Examples/CaretDiagnostics/Program.cs](Examples/CaretDiagnostics/Program.cs). |
| 214 | + |
| 215 | +## Examples |
| 216 | + |
| 217 | +The repository includes small example programs you can run directly: |
| 218 | + |
| 219 | +```sh |
| 220 | +dotnet run --project Examples/PrintHelp/PrintHelp.csproj |
| 221 | +dotnet run --project Examples/ParseOptions/ParseOptions.csproj -- --helo file.cs |
| 222 | +dotnet run --project Examples/CaretDiagnostics/CaretDiagnostics.csproj |
| 223 | +``` |
| 224 | + |
| 225 | +Other examples live in [`Examples/`](Examples). |
| 226 | + |
| 227 | +## Building and testing |
| 228 | + |
| 229 | +Build the solution: |
| 230 | + |
| 231 | +```sh |
| 232 | +dotnet build Pixie.sln |
| 233 | +``` |
| 234 | + |
| 235 | +Run the test suite: |
| 236 | + |
| 237 | +```sh |
| 238 | +dotnet test Tests/Tests.csproj |
| 239 | +``` |
| 240 | + |
| 241 | +## Repository layout |
| 242 | + |
| 243 | +| Path | Contents | |
| 244 | +| --- | --- | |
| 245 | +| [`Pixie/`](Pixie) | Core library: logs, markup nodes, diagnostics, options, and transforms. | |
| 246 | +| [`Pixie.Terminal/`](Pixie.Terminal) | Terminal rendering and device-specific behavior. | |
| 247 | +| [`Pixie.Loyc/`](Pixie.Loyc) | Loyc interoperability layer. | |
| 248 | +| [`Examples/`](Examples) | Small runnable sample applications. | |
| 249 | +| [`Tests/`](Tests) | NUnit test suite. | |
| 250 | +| [`docs/img/`](docs/img) | SVG assets used in this README. | |
| 251 | + |
| 252 | +## Customization |
| 253 | + |
| 254 | +Pixie is designed to be extensible. You can: |
| 255 | + |
| 256 | +* compose markup trees from reusable node types, |
| 257 | +* configure or replace renderers, |
| 258 | +* wrap logs in transforms, and |
| 259 | +* define your own markup elements and rendering behavior. |
| 260 | + |
| 261 | +The examples in [Examples/CaretDiagnostics/Program.cs](Examples/CaretDiagnostics/Program.cs) and [Examples/ParseOptions/Program.cs](Examples/ParseOptions/Program.cs) show some of that flexibility in practice. |
130 | 262 |
|
131 | 263 | ## Contributing |
132 | 264 |
|
133 | | -Thank you so much for considering to contribute! Feel free to fork the Pixie repository and send me a pull request—I'd love to merge it in! It'd also be nice if you opened an issue beforehand so we can talk about your wonderful new feature. |
| 265 | +Issues, questions, and pull requests are all welcome. |
| 266 | + |
| 267 | +If you want to contribute code: |
| 268 | + |
| 269 | +1. Open an issue first for larger changes so the direction is clear. |
| 270 | +2. Build the solution with `dotnet build Pixie.sln`. |
| 271 | +3. Run tests with `dotnet test Tests/Tests.csproj`. |
134 | 272 |
|
135 | | -Alternatively, if you think you found a bug or just have a question about Pixie: open an issue. |
| 273 | +Bug reports and usage questions are also welcome in the issue tracker. |
0 commit comments