output

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: Apache-2.0 Imports: 8 Imported by: 0

README

output

Go packages for producing program output in more than one shape: aligned text, CSV, JSON or YAML, chosen when the program runs.

table builds tabular data and renders it as aligned text.

import "github.com/wisborg/output/table"

t := table.New(
    table.Column{Header: "clip"},
    table.Column{Header: "offset", Align: table.Right, Format: "%+.2fs"},
    table.Column{Header: "score", Align: table.Right, Format: "%.3f"},
)
t.MustAppend("corner_1", 2.70, 0.821)
t.MustAppend("corner_3", 2.75, 0.366)

fmt.Print(t)
clip       offset   score
-------------------------
corner_1   +2.70s   0.821
corner_3   +2.75s   0.366

Style{Frame: true} draws it in the style of the mysql client; Style{Multiline: true} gives each line of a multi-line cell its own row; Style{Spacing: n} sets the gap between columns.

The same table writes CSV, over the same rows:

t.WriteCSV(os.Stdout, table.CSVStyle{})
clip,offset,score
corner_1,+2.70s,0.821
corner_3,+2.75s,0.366

Align and MaxWidth do not apply there — padding and truncation are concessions to a fixed-width display, and silently shortening a value on its way into a file someone will compute with is how you lose it. Format does apply, because that is you saying how the value should be written. Separators are skipped, and an empty table still writes its header, where the text renderer writes nothing: a program reading CSV usually needs that line.

Choosing the format when the program runs

The root package writes one result in whichever format was asked for. The premise is that the program builds two representations of that result and lets the format choose between them:

import "github.com/wisborg/output"

doc := output.Document{
    Data:  results, // the object: for JSON and YAML
    Table: summary, // the simplified table: for text and CSV
}
if err := doc.Write(os.Stdout, format); err != nil {
    return err
}

They are built independently on purpose. Deriving one from the other forces the richer shape through the poorer one and makes both worse: JSON grows stringly-typed cells and pre-formatted numbers, while the table grows columns nobody wanted to read on a terminal. Writing both is a few lines in the program that has the data, and each comes out shaped for its reader — the object can carry the checksum and the detector settings, the table can show three columns and stop.

A Document only needs the representation the chosen format uses: JSON of a document with no Table is fine, CSV of one with no Data is fine. Asking for a format whose representation is missing is an error naming the format — ErrNoData or ErrNoTable — and nothing is written. Nothing is written on any other failure either: whatever the format, the output is either empty or a whole document ending in exactly one newline. A table with no rows is not a missing table: it is a supplied answer that happens to be empty, and it keeps table's own rules, so text writes nothing and CSV writes its header.

Format is a flag.Value, so the flag is one line and an unknown name is refused where the user typed it rather than quietly falling back to text:

format := output.Text // the default; the zero Format is Text
flag.Var(&format, "format", "output format: text, csv, json or yaml")

text, csv, json and yaml, case-insensitively, plus table for text and yml for YAML. Format(99), from a cast or a decoded config file, is an error too: there is no default: arm falling through to text, because a program that prints a table when its caller asked for JSON has produced output that cannot be parsed and no message saying why.

JSON and YAML

WriteJSON and WriteYAML are the same encoders without the Document, for a program that has only the object. Both end their output with exactly one newline, as text and CSV do.

JSON is indented two spaces by default, and does not escape HTML, which inverts encoding/json's default. That default is right when the JSON is embedded in a web page and wrong here: a URL in a CLI's output should read https://x/?a&b, not https://x/?a\u0026b, which stays wrong when the reader copies it out of their terminal. JSONStyle{EscapeHTML: true} puts it back. Marshalling errors are wrapped, not flattened, so errors.As(err, new(*json.UnsupportedTypeError)) still reaches the type that could not be encoded.

YAML is indented two spaces, against the library's default of four, and gets no --- marker for a single document. YAMLStyle{Indent: n} takes 2 to 9, which is the emitter's own range: it resets anything outside that to 2 rather than clamping to the nearer bound, so Indent: 10 would produce output identical to never having set it. That is refused with an error naming the value and the range, rather than being clamped to 9 — a quieter version of the same surprise.

WriteYAML writes all of a document or none of it. The encoder streams, so a value it rejects part-way through would otherwise leave tens of kilobytes of truncated YAML on the writer; the document is rendered into a buffer first and copied out only once it is known to be good. That keeps the invariant above true for every format instead of true for three of them.

Two things about YAML are worth knowing before you offer it as a format:

  • The library panics on a type it cannot marshal — a channel or a function — where encoding/json returns an error. WriteYAML recovers that one panic and returns an error naming the type. The recovery is narrow: any other panic is re-raised, because anything else is a bug in the encoder and swallowing it would hide it.
  • A cyclic value still takes the process down, and cannot be fixed here. The encoder follows pointers with no visited set, so a value pointing back at itself is encoded again at every level, for ever. It does not fail quickly: it spins, emitting an ever-deeper nesting of the same data and consuming CPU and memory without bound, until the buffer cannot grow or the recursion overflows the stack — and a stack overflow in Go is fatal, not a panic, not recoverable. encoding/json detects cycles and reports them as errors, so the same Document that writes as JSON can take the program down as YAML. Break cycles before handing data to any encoder.

Both encoders sort mapping keys their own way — encoding/json alphabetically for maps and in declaration order for structs, the YAML library likewise — and this package adds no knob for that.

Three decisions worth knowing about

Cells are stored as you pass them, and formatted only when rendered. An int stays an int until there is a table to draw. That is what allows a column's alignment, format verb or width limit to be changed after its rows are already in place — the thing you most often want, because you learn what a column holds by looking at it:

t.Columns[1].Align = table.Right

It is also what leaves room for renderers other than text. CSV shares this row model exactly. JSON and YAML deliberately do not: they are far more flexible than a grid of cells, so forcing them through a row-and-column model would make both worse. They read a separate data source instead — see below — and are never derived from a table.

Column widths are measured in terminal columns, not bytes or runes. These are three different numbers. len("café") is 5 or 6 depending on whether the é is precomposed; utf8.RuneCountInString("日本語") is 3 where the terminal uses 6; an emoji with a skin-tone modifier is two runes and one glyph. Getting this wrong produces a table that looks correct until someone's data is not ASCII. The measure is Table.Width, defaulting to runewidth.StringWidth, and it is a field you can replace:

t.Width = utf8.RuneCountInString // fine for ASCII and precomposed Latin

An empty table renders as nothing — not a lone header over blank space, which reads as data that failed to load. Only the caller knows what "no rows" means for them, so only the caller should say it.

Status

Text and CSV in table; text, CSV, JSON and YAML through output.Document.

JSON and YAML are still not table renderers, and that is the same decision as before rather than a reversal of it: nothing converts a table into an object, there is no row accessor for a converter to use, and asking a Document for JSON reads Data and never looks at Table. What has been added is the other data source that argument always implied, and the dispatch that picks between the two.

The API is not yet frozen.

Dependencies

Two, both compatible with this project's Apache-2.0 licence:

  • mattn/go-runewidth (MIT), for display-width measurement, which in turn uses clipperhouse/uax29 (MIT).
  • go.yaml.in/yaml/v3 (MIT and Apache-2.0 — the eight files ported from libyaml are MIT, the rest is Apache-2.0, and there is a NOTICE file), for YAML. This is the YAML organisation's maintained fork of gopkg.in/yaml.v3, which was archived in April 2025. It has no dependencies of its own.

table does not import the YAML library, so a program that only builds tables does not link it.

Licence

Apache License 2.0 — see LICENSE.

Documentation

Overview

Package output writes one result in whichever shape was asked for: aligned text, CSV, JSON or YAML.

The premise is that a program builds two representations of the same result and lets the requested Format choose between them:

  • a rich object, for JSON and YAML, where nesting, types and optional fields all survive; and
  • a deliberately simplified table, for text and CSV, where a person reading a terminal wants a handful of columns and not the object.

The two are built independently, on purpose. The alternative -- deriving the table from the object, or the object from the table -- forces the richer shape through the poorer one and makes both worse: JSON grows stringly-typed cells and pre-formatted numbers, while the table grows columns nobody wanted to read. Writing both is a few lines in the program that has the data, and each comes out shaped for its reader.

doc := output.Document{Data: result, Table: summary}
if err := doc.Write(os.Stdout, format); err != nil {
	return err
}

A Document only needs the representation the chosen format uses: JSON of a Document with no Table is fine, and CSV of one with no Data is fine. Asking for a format whose representation is missing is an error naming the format, and nothing is written.

WriteJSON and WriteYAML are the same encoders without the Document, for a program that has only the object.

Example

Example builds both representations of the same result -- the object for JSON and YAML, a simplified table for text and CSV -- and lets the format choose. The table deliberately leaves out the detector: it is detail the machine wants and the person reading a terminal does not.

package main

import (
	"fmt"
	"os"

	"github.com/wisborg/output"
	"github.com/wisborg/output/table"
)

// probe is the result of one comparison: the object a machine gets.
type probe struct {
	Clip     string  `json:"clip" yaml:"clip"`
	Offset   float64 `json:"offset" yaml:"offset"`
	Score    float64 `json:"score" yaml:"score"`
	Detector string  `json:"detector" yaml:"detector"`
}

func main() {
	results := []probe{
		{Clip: "corner_1", Offset: 2.70, Score: 0.821, Detector: "phase-correlation"},
		{Clip: "corner_3", Offset: 2.75, Score: 0.366, Detector: "phase-correlation"},
	}

	summary := table.New(
		table.Column{Header: "clip"},
		table.Column{Header: "offset", Align: table.Right, Format: "%+.2fs"},
		table.Column{Header: "score", Align: table.Right, Format: "%.3f"},
	)
	for _, r := range results {
		summary.MustAppend(r.Clip, r.Offset, r.Score)
	}

	doc := output.Document{Data: results, Table: summary}

	if err := doc.Write(os.Stdout, output.Text); err != nil {
		fmt.Println("write:", err)
	}

	// The same document as JSON is the object, detector and all.
	if err := doc.Write(os.Stdout, output.JSON); err != nil {
		fmt.Println("write:", err)
	}
}
Output:
clip       offset   score
-------------------------
corner_1   +2.70s   0.821
corner_3   +2.75s   0.366
[
  {
    "clip": "corner_1",
    "offset": 2.7,
    "score": 0.821,
    "detector": "phase-correlation"
  },
  {
    "clip": "corner_3",
    "offset": 2.75,
    "score": 0.366,
    "detector": "phase-correlation"
  }
]

Index

Examples

Constants

View Source
const DefaultYAMLIndent = 2

DefaultYAMLIndent is the number of spaces per nesting level when YAMLStyle.Indent is 0. Two is the prevailing convention, and the one Kubernetes, Docker Compose and most hand-written YAML use; the library's own default is four.

Variables

View Source
var (
	// ErrNoData is returned when a format that writes Document.Data is
	// asked for and Data is nil.
	ErrNoData = errors.New("output: no Data")
	// ErrNoTable is returned when a format that writes Document.Table is
	// asked for and Table is nil.
	ErrNoTable = errors.New("output: no Table")
	// ErrUnknownFormat is the sentinel behind every "this is not a format
	// I know" error, whether the format arrived as a string from a flag or
	// as an out-of-range Format value.
	ErrUnknownFormat = errors.New("output: unknown format")
)

The sentinels every error from this package can be tested for with errors.Is. Each returned error wraps one of these and adds what was actually wrong -- which format was asked for, or what was typed.

View Source
var ErrYAMLIndent = errors.New("output: unsupported YAML indent")

ErrYAMLIndent is returned for a YAMLStyle.Indent the emitter would not honour. See YAMLStyle.Indent for the accepted range.

Exported, where errUnsupportedYAMLType is not, because the two differ in how firm a promise they can make. This one is this package's own check on its own field: it fires exactly when we say it does. The other depends on recognising an upstream panic by its text, so a caller matching on it would be relying on a dependency's private wording.

Functions

func WriteJSON

func WriteJSON(w io.Writer, v any, style JSONStyle) error

WriteJSON writes v as JSON, followed by exactly one newline.

A nil v writes "null" -- JSON's own spelling of "no value", and the only honest rendering of the argument it was given. Note the deliberate asymmetry with Document.Write, where a nil Data is instead an error wrapping ErrNoData: there the nil is a field nobody filled in, which is far more often a bug than a value, and the Document knows which format needed it. Here the value is the argument, stated by the caller, so writing it is what was asked for.

Object keys are sorted by encoding/json (map keys alphabetically, struct fields in declaration order); there is no knob for that here.

Marshalling errors are wrapped with %w rather than flattened into text, because encoding/json's error types carry things a caller can act on: *json.UnsupportedTypeError names the offending type, *json.MarshalerError names the type whose MarshalJSON failed and unwraps to its error, and *json.UnsupportedValueError covers NaN, +Inf and a cyclic value.

Example

ExampleWriteJSON writes an object without a Document, for a program that has no table to offer. Note that HTML characters are left alone, unlike encoding/json's default: this output is for a terminal or for jq.

package main

import (
	"fmt"
	"os"

	"github.com/wisborg/output"
)

func main() {
	v := map[string]string{"url": "https://example.test/?a=1&b=2"}
	if err := output.WriteJSON(os.Stdout, v, output.JSONStyle{}); err != nil {
		fmt.Println("write:", err)
	}
	if err := output.WriteJSON(os.Stdout, v, output.JSONStyle{Compact: true}); err != nil {
		fmt.Println("write:", err)
	}
}
Output:
{
  "url": "https://example.test/?a=1&b=2"
}
{"url":"https://example.test/?a=1&b=2"}

func WriteYAML

func WriteYAML(w io.Writer, v any, style YAMLStyle) error

WriteYAML writes v as YAML, indented per style, followed by exactly one newline. There is no leading "---": a single document does not need one, and it is noise in the common case of a program printing one result.

A nil v writes "null", exactly as WriteJSON does, and for the same reason: the value is the argument the caller passed, and null is YAML's word for it. See WriteJSON for the asymmetry with Document.Write, where a nil Data is instead an error wrapping ErrNoData.

Mapping keys are ordered by the YAML library: map keys in a sorted order of its own, struct fields in declaration order. There is no knob for that here.

The document is encoded in memory first

The YAML encoder streams, so a failure part-way through an encode has already put a large fragment of a document on the writer -- valid-looking YAML, truncated mid-value, with no trailing newline. WriteYAML therefore encodes into a buffer and copies to w only once the whole document is known to be good, which makes it atomic in the way encoding/json already is: on any error, nothing is written at all. That is what lets Document.Write promise that output is either empty or ends in exactly one newline, rather than promising it for three formats and excusing the fourth. The cost is holding the rendered document in memory, which for program output is a document a person or a pipeline was about to read anyway.

A type YAML cannot encode is an error here, not a panic

The underlying encoder PANICS on a value it cannot marshal -- a channel or a function, where encoding/json returns *json.UnsupportedTypeError. A library where the value of a --format flag decides whether the program crashes is not one you can build a CLI on, so WriteYAML recovers that one panic and returns it as an error wrapping an unexported sentinel. The recovery is deliberately narrow: only a panic whose value is a string beginning "cannot marshal type: " is converted, and anything else is re-panicked, because anything else is a bug in the encoder and swallowing it would hide it.

A cyclic value still takes the process down

This cannot be fixed here. The YAML encoder follows pointers with no visited set, so a value that points back at itself is encoded again at each level, for ever. What that looks like in practice is not a prompt crash: the encoder spins, emitting an ever-deeper nesting of the same data and consuming CPU and memory without bound, until the buffer can no longer grow or the recursion overflows the stack. Neither end is recoverable -- a stack overflow is fatal in Go, not a panic, and recover cannot see it -- and buffering does not change that, it only moves the unbounded growth from the writer into memory. encoding/json detects cycles and reports them as errors, so the same Document that writes as JSON can take the program down as YAML.

The alternative, a reflective pre-walk of every value looking for cycles, would cost that walk on every write to defend against a shape that almost never occurs in the data a program actually prints, and would have to track the same visited set for types this package knows nothing about. If your data can contain cycles -- a doubly linked list, a parent pointer, a graph -- break them before handing it to any encoder.

Example
package main

import (
	"fmt"
	"os"

	"github.com/wisborg/output"
)

// probe is the result of one comparison: the object a machine gets.
type probe struct {
	Clip     string  `json:"clip" yaml:"clip"`
	Offset   float64 `json:"offset" yaml:"offset"`
	Score    float64 `json:"score" yaml:"score"`
	Detector string  `json:"detector" yaml:"detector"`
}

func main() {
	v := probe{Clip: "corner_1", Offset: 2.70, Score: 0.821, Detector: "phase-correlation"}
	if err := output.WriteYAML(os.Stdout, v, output.YAMLStyle{}); err != nil {
		fmt.Println("write:", err)
	}
}
Output:
clip: corner_1
offset: 2.7
score: 0.821
detector: phase-correlation

Types

type Document

type Document struct {
	// Data is the object written by JSON and YAML. nil means "not
	// supplied", and asking for one of those formats is then an error
	// wrapping ErrNoData rather than a document reading "null".
	//
	// Only a nil interface counts as absent. Data holding a TYPED nil
	// pointer -- a (*Result)(nil) -- is a value, and marshals as null in
	// both formats, because at that point the program has said what it has
	// and null is the accurate answer.
	Data any

	// Table is the table written by Text and CSV. nil means "not
	// supplied", and asking for one of those formats is then an error
	// wrapping ErrNoTable.
	//
	// A table with no rows is NOT the same thing: it is a supplied answer
	// that happens to be empty, and it follows the table package's own
	// rules -- text writes nothing at all, CSV still writes its header.
	Table *table.Table

	// TextStyle is the style Text is rendered in. The zero value is the
	// plain unframed table.
	TextStyle table.Style

	// CSVStyle is the style CSV is written in. The zero value is ordinary
	// comma-separated data with a header row.
	CSVStyle table.CSVStyle

	// JSONStyle is the style JSON is written in. The zero value is
	// indented, without HTML escaping.
	JSONStyle JSONStyle

	// YAMLStyle is the style YAML is written in. The zero value is
	// two-space indentation.
	YAMLStyle YAMLStyle
}

Document is one result in both of its representations, plus the styles each format is written in. The zero Document holds neither representation and can be written in no format; fill in the ones your program produces.

func (Document) Write

func (d Document) Write(w io.Writer, f Format) error

Write writes the document to w in format f.

Only the representation f needs is required: JSON and YAML use Data, text and CSV use Table. A missing one is an error wrapping ErrNoData or ErrNoTable that names the format asked for, and nothing is written -- the check happens before the first byte, so a caller that reports the error and exits has not already put half a document on the terminal. The same holds for a value the encoder rejects part-way through: JSON and YAML both write all of a document or none of it.

A Format outside the defined set is an error wrapping ErrUnknownFormat. There is deliberately no fallback to text: the format usually comes from a flag, and a program that prints a table when its caller asked for JSON has produced output that cannot be parsed and no message saying why.

The receiver is a value because Write reads the Document and changes nothing in it. Copying the struct copies the Table pointer, not the table.

Whatever the format, the output is either empty or ends in exactly one newline.

Example (Json)
package main

import (
	"fmt"
	"os"

	"github.com/wisborg/output"
)

// probe is the result of one comparison: the object a machine gets.
type probe struct {
	Clip     string  `json:"clip" yaml:"clip"`
	Offset   float64 `json:"offset" yaml:"offset"`
	Score    float64 `json:"score" yaml:"score"`
	Detector string  `json:"detector" yaml:"detector"`
}

func main() {
	doc := output.Document{
		Data: probe{Clip: "corner_1", Offset: 2.70, Score: 0.821, Detector: "phase-correlation"},
	}
	if err := doc.Write(os.Stdout, output.JSON); err != nil {
		fmt.Println("write:", err)
	}
}
Output:
{
  "clip": "corner_1",
  "offset": 2.7,
  "score": 0.821,
  "detector": "phase-correlation"
}
Example (Missing)

ExampleDocument_Write_missing shows what happens when the format asks for a representation the Document does not have. Nothing is written, and the error names the format and can be tested for with errors.Is.

package main

import (
	"errors"
	"fmt"
	"os"

	"github.com/wisborg/output"
)

// probe is the result of one comparison: the object a machine gets.
type probe struct {
	Clip     string  `json:"clip" yaml:"clip"`
	Offset   float64 `json:"offset" yaml:"offset"`
	Score    float64 `json:"score" yaml:"score"`
	Detector string  `json:"detector" yaml:"detector"`
}

func main() {
	doc := output.Document{Data: probe{Clip: "corner_1"}} // no Table

	err := doc.Write(os.Stdout, output.CSV)
	fmt.Println(err)
	fmt.Println(errors.Is(err, output.ErrNoTable))
}
Output:
output: no Table to write as csv
true
Example (Yaml)
package main

import (
	"fmt"
	"os"

	"github.com/wisborg/output"
)

// probe is the result of one comparison: the object a machine gets.
type probe struct {
	Clip     string  `json:"clip" yaml:"clip"`
	Offset   float64 `json:"offset" yaml:"offset"`
	Score    float64 `json:"score" yaml:"score"`
	Detector string  `json:"detector" yaml:"detector"`
}

func main() {
	doc := output.Document{
		Data: probe{Clip: "corner_1", Offset: 2.70, Score: 0.821, Detector: "phase-correlation"},
	}
	if err := doc.Write(os.Stdout, output.YAML); err != nil {
		fmt.Println("write:", err)
	}
}
Output:
clip: corner_1
offset: 2.7
score: 0.821
detector: phase-correlation

type Format

type Format int

Format selects which shape a Document is written in.

The zero value is Text, so a Format field that nobody set is the plain human-readable rendering -- the right default for a command-line program, and the one that makes a struct literal with no Format in it do something sensible.

const (
	// Text is aligned text for a human to read, from the Document's Table.
	Text Format = iota
	// CSV is comma-separated data, from the Document's Table.
	CSV
	// JSON is JSON, from the Document's Data.
	JSON
	// YAML is YAML, from the Document's Data.
	YAML
)

func Formats

func Formats() []Format

Formats returns every defined format, in declaration order. It is for building a flag's help text or a menu without hard-coding the list a second time and letting the two drift apart.

Each call returns a fresh slice, so a caller may sort or filter it without affecting anyone else.

func ParseFormat

func ParseFormat(s string) (Format, error)

ParseFormat turns a format name into a Format. Surrounding space is trimmed and case is ignored, so "JSON", " json" and "json" are one thing: the name usually comes from a human typing a flag.

The canonical names are those of Format.String; "table" is also accepted for Text and "yml" for YAML.

An unrecognised name is an error wrapping ErrUnknownFormat. There is deliberately no fallback to Text: a program that silently prints a table when asked for JSON produces output its caller cannot parse and no message saying why.

func (Format) MarshalText

func (f Format) MarshalText() ([]byte, error)

MarshalText makes Format encode as its canonical name in JSON, YAML and anything else built on encoding.TextMarshaler -- so a Format inside a config struct is "json" rather than the integer 2, which would be both unreadable and silently wrong the day a format is inserted into the middle of the constant block.

func (*Format) Set

func (f *Format) Set(s string) error

Set parses s into f, making *Format a flag.Value. A format flag is then one line, validated where the flag is parsed rather than where it is used:

format := output.JSON // the default
flag.Var(&format, "format", "output format")

Because Format.String is the canonical name, the flag package prints the default in the same spelling Set accepts.

Example

ExampleFormat_Set uses a Format as a flag value. Set validates at parse time, so an unknown name is rejected where the user typed it rather than silently falling back to text.

package main

import (
	"flag"
	"fmt"
	"io"

	"github.com/wisborg/output"
)

func main() {
	format := output.Text // the default

	fs := flag.NewFlagSet("probe", flag.ContinueOnError)
	fs.SetOutput(io.Discard) // the usage message is not the point here
	fs.Var(&format, "format", "output format")
	if err := fs.Parse([]string{"-format", "YAML"}); err != nil {
		fmt.Println("parse:", err)
	}
	fmt.Println(format)

	if err := fs.Parse([]string{"-format", "xml"}); err != nil {
		fmt.Println(err)
	}
}
Output:
yaml
invalid value "xml" for flag -format: output: unknown format "xml" (accepted: text, csv, json, yaml, table, yml)

func (Format) String

func (f Format) String() string

String returns the format's canonical name: "text", "csv", "json" or "yaml". These are the names ParseFormat accepts, so a value that survives String and ParseFormat is unchanged, and a flag's default can be printed with the same spelling the user has to type.

A Format outside the defined set renders as "Format(n)", following the convention of the standard library and of table.Align: an invalid value should be conspicuous in a message rather than pretending to be text.

func (*Format) UnmarshalText

func (f *Format) UnmarshalText(b []byte) error

UnmarshalText parses a format name, with the same spellings ParseFormat takes.

type JSONStyle

type JSONStyle struct {
	// Compact writes the whole value on one line with no indentation, for
	// a log line or for JSON Lines. The default is indented, because the
	// usual reader of a CLI's JSON is a person looking at a terminal, and
	// the machine reader does not care either way.
	Compact bool

	// EscapeHTML escapes <, > and & as \u003c, \u003e and \u0026.
	//
	// The zero value does NOT escape, which inverts encoding/json's
	// default. That default exists because the standard library's JSON is
	// often embedded in HTML, where those characters can end a script
	// element early. Program output is not: it goes to a terminal or into
	// jq, and there a URL comes out as "https://x/?a\u0026b", which is
	// wrong on sight and stays wrong when the reader copies it. Set this
	// when the JSON really is destined for a web page.
	EscapeHTML bool
}

JSONStyle controls JSON output. The zero value is what a command-line program wants: indented, and with HTML escaping off.

type YAMLStyle

type YAMLStyle struct {
	// Indent is the number of spaces per nesting level. 0 means
	// DefaultYAMLIndent, so the zero YAMLStyle is the conventional
	// rendering and this field only has to be set to depart from it.
	//
	// Any other value must be between 2 and 9. That ceiling is the YAML
	// emitter's, not this package's: it accepts 2 through 9 and RESETS
	// anything else to 2 -- not to the nearer bound, to 2 -- so
	// YAMLStyle{Indent: 10} would silently produce output identical to
	// never having set Indent at all. WriteYAML therefore rejects an
	// out-of-range Indent with an error naming the value and the range,
	// and writes nothing. Clamping 10 to 9 was considered and rejected: it
	// is a quieter version of the same surprise, and a caller who asked
	// for ten spaces is working from an assumption worth correcting rather
	// than approximating.
	Indent int
}

YAMLStyle controls YAML output. The zero value is two-space indentation.

Directories

Path Synopsis
Package table builds tabular data and renders it as aligned text.
Package table builds tabular data and renders it as aligned text.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL