Presentation
For human readable output, it is desirable for consumers as well as the framework to be able to:
- Output arbitrary types.
- Have them formatted by the output implementation.
- Not require the output implementation to know the specific type that is being output.
The OutputWrite
trait was written to handle different output formats – human-readable vs CI logging vs structured text – but not how to present the output.
Peace should provide two traits:
-
OutputWrite
: Maps between the information and the output format, e.g. human readable vs parseable.Examples:
- CLI output: Writes progress as log messages (CI), or progress bar (interactive), or nothing (when piped).
- Web output: Write JSON.
- Native application: Update a UI component.
-
Presentable
: Maps between the information and the presentation format, e.g. present this as an ID, short text, long text, a list, etcetera.Examples:
- CLI output: Colour text with ANSI colour codes.
- Web output: Create and style web elements.
- Native application: Create and style UI components.
Current State
The OutputWrite
trait has methods for:
- writing progress: setting up state, updating progress, and ending.
- writing states (current, goal, diff, etc.)
- writing error
These methods are specific to State
s, and if we add methods per type, it doesn't allow any arbitrary type to be formatted and written.
Goal State
To be usable with arbitrary information, OutputWrite
should have methods to output different kinds of information. These information kinds are based on the purpose of the information, not on how they should be grouped or presented.
Information Kinds
-
Progress: Information about the execution of automation.
-
Outcome: Information that the automation is purposed to produce.
-
Notes: Meta information about the outcome or progress -- informatives, warnings.
These can be used to refine the automation.
For each information kind, OutputWrite
should be able to:
- Write one or many of that information kind
- Reason over the parameters of that information, and potentially pass it to a formatter.
For structured output, all information should be serializable.
Presentation / Formatting
For human readable output to be understandable, the OutputWrite
implementation should improve clarity by adding styling or reducing information overload. For this to work with arbitrary types, the OutputWrite
needs additional hints to determine how to format the information.
Examples:
- An object may be presented as a list, and the type needs to define which fields that list is built from.
- When presenting a list of named items, the type needs to define both the name and the description, which allows the names to be styled differently to the descriptions.
- When presenting a large object, the density of information can be reduced through collapsible sections, and more detail displayed when the sections are expanded.
Implementation
To achieve this, we can:
-
Define a
peace::fmt::Presentable
trait, analogous tostd::fmt::Display
-
Define a
peace::fmt::Presenter
trait, analogous tostd::fmt::Formatter
-
Presenter
has methods to format:- short text descriptions
- long text descriptions (e.g. always split at
\n
s) - names, e.g. always bold
- lists of
Presentable
s - groups, e.g. always collapsible, or presenter may choose to not display if level of detail is too high
-
Implementors will
impl Presentable for MyType
. This can be made easier with a derive macro. -
Update
OutputWrite
to take in&dyn Presentable
instead of concrete types, and theOutputWrite
implementation can decide whether or not to delegate toPresenter
for presentation information. e.g. a serializing output write may not need to.
Note: Structured output that is read by humans (e.g. prettified YAML or JSON) is not a peace::fmt::Presentable
concern, but an OutputWrite
parameter, as it is a standard format serialization parameter, not formatting hints that the output endpoint needs.
Instead of using &str
s for what is presented, we could add type safety to:
- Enforce certain constraints, e.g. short descriptions must be one line, less than 200 characters
- For human readable output, instead of
std::fmt::Display
, types implementpeace::fmt::Presentable
trait where apeace::fmt::Presenter
is passed in.
The type safety can be opt-in, e.g. allow &str
s, but if using the type-safe wrappers, you get compilation errors when the constraints are not met.
Recursion
If the Presentable
trait is recursive like Debug
, then we need to make sure implementors understand that a "name" will always be styled as a name, unless one creates a wrapping type that does not delegate to the name's underlying Presentable
implementation (just like Debug
).