Skip to content

Commit 5a6ef37

Browse files
Updated after conversations with @patriksvensson and @mhutch.
1 parent ccf175f commit 5a6ef37

File tree

1 file changed

+19
-47
lines changed

1 file changed

+19
-47
lines changed

docs/Proposals/powderhouse-output.md

+19-47
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,34 @@
1-
[Proposal] Output in Powderhoue
1+
# [Proposal] Output in Powderhouse
22

33
Output for the Powderhouse version of System.CommandLine will be used in these scenarios:
44

55
* Help
66
* Error reporting
77
* Optionally by applications using System.CommandLine
88

9-
While it is reasonable to allow System.CommandLine apps to access it's outputting system to provide consistent output, it is a not a goal to create a general outputting approach for .NET.
9+
While it is reasonable to allow System.CommandLine apps to access its outputting system to provide consistent output, it is a non-goal to create a general outputting approach for .NET.
1010

11-
Output will be via an abstraction to allow outputting to multiple formats from a single definition, such as output to plain text, fancy console, or markdown and orthogonally to multiple locations (console, files in different formats). This will result from multiple parts:
11+
Output will go to a TextWriter, either the standard TextWriter or one derived from it that has overloads that take other types - such as the layout types described here and supports a formatter model to handle them. This alternate design has a few advantages, including moving the project forward without this work, and in the future allowing simple output to be simple.
12+
13+
Output will optionally allow outputting to multiple formats from a single definition, such as output to plain text, fancy console, or markdown and orthogonally to multiple locations (console, files in different formats). This will result from multiple parts:
1214

1315
* Content
14-
* Layout
15-
* Formatting
16-
* Output
16+
* Layout (Optional)
17+
* Output/TextWriter
18+
* With formatting (Optional)
19+
20+
The content will be plain text, text with ANSI codes, System.CommandLine types, or subsystem types. The output will be a TextWriter, which is an explicit decision not to support binary output with this API. The rest of this proposal focuses on layout and formatting.
21+
22+
## Output
23+
24+
Output will be to a TextWriter. We anticipate a StreamWriter under the hood that can handle writing to StdOut/StdError, memory, files, etc.
1725

18-
The content will be the System.CommandLine and subsystem types. The output may be as simple as a stream. The rest of this proposal focuses on layout and formatting.
26+
The TextWriter can be a FormattingTextWriter which can handle layout types, such as those described below. These will be created as they add value to the effort.
1927

2028
## Layout
2129

30+
Layout is tied to formatters, and are optional. Code can write directly to the TextWriter.
31+
2232
Layout will be via an open set of blocks:
2333

2434
* TextBlock
@@ -101,55 +111,17 @@ For example, should the table for errors, options, and arguments (or commands in
101111

102112
## Formatters
103113

104-
Formatters will output one or more block types and one or more output formats.
105-
106-
_Note: Multiple formats allow simpler implementation of formatters for custom blocks, and will also make it easier to create custom fallback output._
114+
Formatters are needed when layout blocks are used, and otherwise optional. They are managed by a TextWriter.
107115

108116
Scenarios are:
109117

110118
* Formatters that implement a consistent look and supplies output for some or all known block types for a specific output look (such as Spectre)
111119
* Formatters that override a specific block type for a custom look (such as customizing all tables)
112120
* Formatters that override a specific block (such as customizing only the option block)
113121

114-
### Formatter API
115-
116-
```csharp
117-
public abstract class Formatter
118-
// possible configuration settings.
119-
public abstract bool CanHandle(Block block, string formatId);
120-
public abstract void Output(IEnumerable<Formatter> formatters, Stream stream, int treeLevel, Block block, string formatId);
121-
```
122-
123-
The formatter is expected to use pattern matching to make falling back easier. An example for part of help to terminal (this can be thrown out by implementor, but illustrates a couple of details):
124-
125-
```csharp
126-
public class TerminalFormatter
127-
{
128-
public void Output(IEnumerable<Formatter> formatters, Stream stream, int treeLevel, Block block, string formatId)
129-
{
130-
131-
if (block is Section section)
132-
{
133-
// Terminal ignores treeLevel
134-
stream.WriteLine(section.Title); // assuming an extension method called WriteLine
135-
foreach (var childBlock in section.Blocks)
136-
{
137-
Output(stream, treeLevel, childBlock, formatId);
138-
this.CanHandle(block, formatId)) // Container may handle block while child does not
139-
? Output(formatters, stream, treeLevel, block, formatId);
140-
: formatters.Output(formatters, stream, treeLevel, block, formatId);
141-
}
142-
}
143-
// ... Other block types
144-
}
145-
}
146-
```
147-
148122
Formatters are per block type, or block. This is important to allow customization of details without needing to copy the entire formatter. It also means that the current formatter may not support a block when another formatter does. That is because, as this is written, the current formatter is the one for the parent.
149123

150-
> _Question:_ Should the current formatter have precedence? As this code is written, the current formatter does have precedence. If the conditional for CanHandle within this method is removed, such that formatters.Output is always called, then the current formatter does not have precedence. Thought example: A plain text formatter can supply adequate output for a section title. If the current formatter has precedence, then a terminal formatter would be required to implement sections, or output of the child blocks would be done by the plain text formatter instead of the terminal formatter. OTOH, having to peruse the formatters for every block sounds inefficient, and intuitively, it seems that sticking to the same formatter would result in a more consistent look. Alternative designs would include having a preferred formatter and using it if possible, creating a dictionary of block type and the best formatter, although this would not allow a different formatter for block data contents, or something else.
151-
152-
The formatId is proposed as a string because that is what we can pass straight from the user for custom formats.
124+
> _Question for implementation:_ Should the current formatter have precedence? As this code is written, the current formatter does have precedence. If the conditional for CanHandle within this method is removed, such that formatters.Output is always called, then the current formatter does not have precedence. Thought example: A plain text formatter can supply adequate output for a section title. If the current formatter has precedence, then a terminal formatter would be required to implement sections, or output of the child blocks would be done by the plain text formatter instead of the terminal formatter. OTOH, having to peruse the formatters for every block sounds inefficient, and intuitively, it seems that sticking to the same formatter would result in a more consistent look. Alternative designs would include having a preferred formatter and using it if possible, creating a dictionary of block type and the best formatter, although this would not allow a different formatter for block data contents, or something else.
153125

154126
### No op
155127

0 commit comments

Comments
 (0)