Documentation
¶
Overview ¶
Package output writes a command's result to stdout.
stdout carries the result and nothing else; diagnostics, progress and prompts go to stderr through github.com/tarantool/tt/sdk/log. The result format (--format) is independent of the log format (--log-format).
A command renders its result through a Printer bound to the process streams and the format the user chose. The human format is the command's own rendering, Result.Human; a machine format encodes the result value. A machine-format result is written whole or not at all, so a command that fails leaves stdout empty - there is no error envelope; the error goes to stderr and into the exit code.
The format is chosen by the --format flag alone: a command declares the flag with BindFormat, which takes the formats it accepts and its fixed default, and ResolveFormat maps a flag value to a format. Whether stdout is a terminal never changes the format; it may change only styling - colour, width, truncation - and a command reads it from Printer.Terminal.
The SDK encodes JSON itself. Any other machine format is supplied by the tt core with WithEncoder, which keeps its encoder library out of the SDK.
A result too large to hold - the tuples of a big space, records read from xlogs, a log being followed - is written item by item through a Stream from Printer.Stream. In JSON a stream is JSON Lines: one compact value per line, no enclosing array. In the human format each item renders itself with Result.Human, so columns cannot be aligned across items: an item that is a table row pads its cells to fixed widths, and a heading or a summary is printed with Print before the stream opens or after it closes. Another machine format streams only if the core gives it a stream encoder with WithStreamEncoder; YAML's stream, say, is a sequence of documents.
A stream is not all-or-nothing. Each item is written whole or not at all, and as soon as it is emitted; when a command fails midway the items already written stay on stdout, the error goes to stderr and into the exit code, and the exit code is how a consumer tells a complete stream from a cut one. While a stream is open, Print, Printf, Emit and Stream refuse with ErrStreamOpen, so nothing interleaves with the items.
The JSON encoder passes every value through Normalize first, so a result may carry what a MessagePack decoder produced - maps keyed by interfaces, integers or bools - without the command converting it. A core encoder calls Normalize itself when its format needs it.
A float that is NaN, +Inf or -Inf has no JSON number, so it is written as the string "NaN", "Infinity" or "-Infinity", while a finite float stays a number: the JSON type of such a value depends on the value. This is how encoding/json/v2 writes a float under `json:",format:nonfinite"` and how the proto3 JSON mapping writes a double.
Index ¶
Constants ¶
This section is empty.
Variables ¶
var ( // ErrKeyCollision reports a map in which two distinct keys have the same // string form, such as the integer 1 and the string "1". ErrKeyCollision = errors.New("map keys collide") // ErrNotNormalizable reports a value Normalize cannot make encodable: // a map key with no string form, a struct field whose declared type // cannot hold the normalised value, or a reference cycle. ErrNotNormalizable = errors.New("value cannot be normalised") )
var ( // ErrUnknownFormat reports a format that is neither human nor one the // Printer has an encoder for. ErrUnknownFormat = errors.New("unknown output format") // ErrNoMachineForm reports human-only text printed while a machine format // was chosen. Such output has no machine form: emit a Result instead. ErrNoMachineForm = errors.New("human-only output in a machine format") // ErrNoStream reports streams without a stdout. ErrNoStream = errors.New("no output stream") // ErrNoStreamForm reports a stream requested in a machine format that // has no stream encoder. ErrNoStreamForm = errors.New("output format cannot be streamed") // ErrStreamOpen reports output attempted on a Printer while one of its // streams is open. ErrStreamOpen = errors.New("an output stream is open") // ErrStreamClosed reports an item emitted to a closed stream. ErrStreamClosed = errors.New("output stream is closed") )
Functions ¶
func Normalize ¶
Normalize returns value in a form every machine encoder can take: the shape of a document decoded from MessagePack - a Tarantool tuple, a Lua table - with no map keyed by an interface, a bool or a float. The JSON encoder applies it to every result and stream item; a core encoder for another format calls it when that format needs it.
The rules:
- A map whose key type is an interface, a bool or a float becomes a map[string]any. A string key stays as it is, a TextMarshaler key becomes its text, an integer or a bool its decimal or true/false spelling, a finite float its shortest 'g' formatting and a non-finite one "NaN", "Infinity" or "-Infinity". A key of any other kind, nil included, is ErrNotNormalizable. Maps keyed by strings, integers or TextMarshalers keep their key type, as encoding/json handles them.
- Two keys of one map with the same string form - 1 and "1", NaN and "NaN", two NaNs - are ErrKeyCollision naming both; neither is dropped.
- A float32 or float64 value that is NaN, +Inf or -Inf becomes the string "NaN", "Infinity" or "-Infinity"; a finite float stays a number. JSON has no number for them, and encoding/json refuses them. The JSON type of such a value then depends on the value - a number normally, a string when it is non-finite - which is how encoding/json/v2 encodes a float under `json:",format:nonfinite"` and how the proto3 JSON mapping encodes a double. A struct field declared as a float cannot hold the string, so a non-finite value in one is ErrNotNormalizable.
- []byte is left as it is, so JSON carries it as base64. MessagePack tells a binary value from a string, and a JSON string keeps that distinction only if it does not depend on the content: a binary value that happens to be valid UTF-8 is still binary.
- A value whose type implements json.Marshaler or encoding.TextMarshaler is left to its own marshalling.
- A struct with no exported and no embedded fields that implements fmt.Stringer becomes its String form. encoding/json would render it as {}, losing the value; go-tarantool's datetime.Datetime is such a type.
- Slices, arrays, maps, pointers, interfaces and the exported fields of structs, exported embedded ones included, are walked. A container whose element type cannot hold a normalised element becomes a []any or a map with any values; a struct field that cannot hold its normalised value is ErrNotNormalizable - declare such a field as any. Fields tagged json:"-" and the fields of an unexported embedded struct are not walked. A reference cycle is ErrNotNormalizable.
Normalize never mutates value: whatever it changes is a copy, and what it does not change is shared. A value that needs no change is returned as it is.
Types ¶
type Format ¶
type Format string
Format names how a result is rendered.
const ( // FormatHuman is the command's own rendering for people: a table, a tree, // a sentence. It is spelled "table", as every tt command with a --format // flag spells its human format. FormatHuman Format = "table" // FormatJSON is indented JSON with a trailing newline. FormatJSON Format = "json" // FormatYAML is YAML at a two-space indent; a stream of results is a // sequence of YAML documents. The SDK does not encode it: the core adds // its encoders with WithEncoder and WithStreamEncoder, so a Printer made // without them refuses it with ErrUnknownFormat. FormatYAML Format = "yaml" )
func ResolveFormat ¶
ResolveFormat returns the format a --format flag value selects: the value itself, or fallback - the command's default - when the flag is empty. It is a pure function of its arguments; in particular it does not depend on whether stdout is a terminal. Whether the result names a format the command can render is checked by NewPrinter.
type FormatFlag ¶
type FormatFlag struct {
// contains filtered or unexported fields
}
FormatFlag is a -o/--format flag declared by BindFormat: the format the user named, checked against the formats the command accepts, or the command's default.
func BindFormat ¶
func BindFormat(flags *pflag.FlagSet, fallback Format, formats ...Format) *FormatFlag
BindFormat declares -o/--format on flags, accepting formats and defaulting to fallback, and returns the flag to read the chosen format from once the flags are parsed. Every command that prints a result declares its format flag with BindFormat, so the flag has one name, one shorthand and one help text everywhere: the accepted values in the order given, and the default.
A value outside formats fails flag parsing with ErrUnknownFormat. An empty value selects fallback, as ResolveFormat does. The terminal plays no part: without the flag the format is fallback whatever stdout is.
BindFormat panics when formats is empty, names a format twice or does not include fallback - a mistake in the command's code, not in its input - and, as pflag does, when flags already has a format or o flag.
func (*FormatFlag) Format ¶
func (f *FormatFlag) Format() Format
Format returns the format the flag selects: the value given, or the default when the flag was not given or given empty.
func (*FormatFlag) Set ¶
func (f *FormatFlag) Set(value string) error
Set takes a value from the command line. A format the command does not accept is ErrUnknownFormat.
func (*FormatFlag) String ¶
func (f *FormatFlag) String() string
String returns the selected format; pflag shows it as the default in the help text.
func (*FormatFlag) Type ¶
func (*FormatFlag) Type() string
Type names the flag's value type in the help text.
type NewStreamEncoder ¶
type NewStreamEncoder func(w io.Writer) StreamEncoder
NewStreamEncoder starts a stream encoder writing to w. Anything it writes before returning - a header - reaches stdout when the stream opens.
type Option ¶
type Option func(*Printer)
Option configures a Printer.
func WithEncoder ¶
WithEncoder makes format a machine format rendered by encoder, replacing any encoder the format already has. The core uses it to add formats the SDK does not encode itself, such as YAML.
func WithStreamEncoder ¶
func WithStreamEncoder(format Format, newEncoder NewStreamEncoder) Option
WithStreamEncoder lets format be streamed by the encoders newEncoder starts, replacing any stream encoder the format already has. The format must also have an encoder, built in or given by WithEncoder. JSON streams out of the box; the core adds streaming to the formats it registers, such as YAML.
func WithTerminalProbe ¶
func WithTerminalProbe(probe TerminalProbe) Option
WithTerminalProbe sets how the Printer finds out whether stdout is a terminal. The core passes its isatty check; tests pass a constant. Without a probe stdout counts as not a terminal, which selects plain styling.
type Printer ¶
type Printer struct {
// contains filtered or unexported fields
}
Printer writes results to stdout in one format.
func NewPrinter ¶
NewPrinter returns a Printer that writes to streams.Out in format. The format must be FormatHuman, FormatJSON, or one given an encoder by opts.
func (*Printer) Emit ¶
Emit renders result to stdout: its Human rendering in the human format, the encoded value otherwise. A machine format is encoded in full before anything is written, so an encoding error leaves stdout untouched.
func (*Printer) Print ¶
Print writes human text to stdout, formatted as fmt.Fprint does. It is for a result that exists only as text; in a machine format it writes nothing and returns ErrNoMachineForm.
func (*Printer) Printf ¶
Printf writes human text to stdout, formatted as fmt.Fprintf does. Like Print, it returns ErrNoMachineForm in a machine format.
func (*Printer) Stream ¶
Stream opens a stream on stdout. In the human format every item is rendered by its Human method; in a machine format it is encoded by the format's stream encoder, and a format without one is ErrNoStreamForm. A Printer has at most one open stream: while it is open, Stream, Print, Printf and Emit return ErrStreamOpen.
func (*Printer) Terminal ¶
Terminal reports whether stdout is a terminal, as the probe given by WithTerminalProbe answered when the Printer was made. It is for styling a human rendering - colour, width, truncation - and never for choosing the format.
func (*Printer) Writer ¶
Writer returns stdout itself, for output the Printer does not shape - bytes passed through as they are. The command is then responsible for writing the chosen format; records written as they are read go through Stream instead. Writes through Writer are not checked against an open stream: keeping them apart is the caller's job.
type Result ¶
Result is a command's result.
Human writes the human rendering to w; it writes nothing that is not the result, so a note about the result (a stale lock, say) is logged to stderr instead. A machine format encodes the Result value itself, so its exported fields and struct tags are the machine schema; tt's payloads use snake_case keys.
type Stream ¶
type Stream struct {
// contains filtered or unexported fields
}
Stream writes a result to stdout one item at a time, each as soon as it is emitted. It is opened by Printer.Stream and must be closed; until then the Printer refuses any other output. A Stream is safe for concurrent use: items emitted from several goroutines are written one after another, never interleaved.
func (*Stream) Close ¶
Close ends the stream: the stream encoder writes what follows the last item, and the Printer accepts other output again, even when Close fails. Closing a closed stream does nothing.
func (*Stream) Emit ¶
Emit writes item to stdout: its Human rendering in the human format, its encoding otherwise. The item is rendered in full before anything is written, so an item that fails to render writes nothing, and the stream stays open for the next one. Items emitted before stay on stdout whatever happens after them. When stdout can be flushed it is flushed after every item.
type StreamEncoder ¶
StreamEncoder writes the items of one stream in a machine format.
Encode writes one item; Close ends the stream and writes whatever the format puts after the last item. The writer a StreamEncoder is given collects what one call writes: what Encode wrote reaches stdout when it returns nil, and is discarded when it fails. Encode is not called concurrently.
type Streams ¶
type Streams struct {
// In is where input and prompt answers are read from.
In io.Reader
// Out receives the command's result and nothing else.
Out io.Writer
// Err receives diagnostics, progress and prompts.
Err io.Writer
}
Streams are the standard streams a command works with.
func StdStreams ¶
func StdStreams() Streams
StdStreams returns the process's stdin, stdout and stderr.
type TerminalProbe ¶
TerminalProbe reports whether w is a terminal.