cmd/tailscale/cli/jsonoutput: add support for --format=json
`tailscale netcheck` is the only command that doesn’t support the `--json` flag, but rather requires `--format=json`. This patch adds a flag.Value named jsonoutput.Format that handles a boolean `--json` flag, a versioned `--json=2` flag, and an optional `--format=json-line` flag. Updates #17613 Updates #17366 Updates tailscale/corp#33033 Signed-off-by: Simon Law <sfllaw@tailscale.com>
This commit is contained in:
@@ -0,0 +1,41 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & contributors
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package jsonoutput_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"tailscale.com/cmd/tailscale/cli/jsonoutput"
|
||||||
|
)
|
||||||
|
|
||||||
|
func ExampleFormat_JSONBool() {
|
||||||
|
var args struct {
|
||||||
|
format jsonoutput.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("ExampleFormat", flag.ExitOnError)
|
||||||
|
fs.Var(&args.format, "format", `output format; empty (for human-readable), "json" or "json-line"`)
|
||||||
|
fs.Var(args.format.JSONBool(), "json", "output in JSON format")
|
||||||
|
|
||||||
|
fs.Parse([]string{"-json"})
|
||||||
|
fmt.Printf(`{format: %q, set: %t, version: %d}`, args.format, args.format.IsSet, args.format.Version)
|
||||||
|
// Output:
|
||||||
|
// {format: "json", set: true, version: 1}
|
||||||
|
}
|
||||||
|
|
||||||
|
func ExampleFormat_JSONSchemaVersion() {
|
||||||
|
var args struct {
|
||||||
|
format jsonoutput.Format
|
||||||
|
}
|
||||||
|
|
||||||
|
fs := flag.NewFlagSet("ExampleFormat", flag.ExitOnError)
|
||||||
|
fs.Var(&args.format, "format", `output format; empty (for human-readable), "json" or "json-line"`)
|
||||||
|
fs.Var(args.format.JSONSchemaVersion(), "json", "output in JSON format")
|
||||||
|
|
||||||
|
fs.Parse([]string{"-json=2", "-format=json-line"})
|
||||||
|
fmt.Printf(`{format: %q, set: %t, version: %d}`, args.format, args.format.IsSet, args.format.Version)
|
||||||
|
// Output:
|
||||||
|
// {format: "json-line", set: true, version: 2}
|
||||||
|
}
|
||||||
@@ -19,7 +19,7 @@ func ExampleSchemaVersion() {
|
|||||||
fs.Var(&args.json, "json", "output in JSON format")
|
fs.Var(&args.json, "json", "output in JSON format")
|
||||||
|
|
||||||
fs.Parse([]string{"-json=2"})
|
fs.Parse([]string{"-json=2"})
|
||||||
fmt.Printf(`{set: %t, value: %d}`, args.json.IsSet, args.json.Version)
|
fmt.Printf(`{set: %t, version: %d}`, args.json.IsSet, args.json.Version)
|
||||||
// Output:
|
// Output:
|
||||||
// {set: true, value: 2}
|
// {set: true, version: 2}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & contributors
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package jsonoutput
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"strconv"
|
||||||
|
)
|
||||||
|
|
||||||
|
var _ flag.Value = new(Format)
|
||||||
|
|
||||||
|
// Format implements the [flag.Value] interface,
|
||||||
|
// supporting a combination of both -format and -json flags.
|
||||||
|
//
|
||||||
|
// For some commands, like tailscale netcheck or tailscale routecheck,
|
||||||
|
// the user can specify the output format using the -format flag:
|
||||||
|
//
|
||||||
|
// tailscale routecheck -format=json-line.
|
||||||
|
//
|
||||||
|
// Setting this flag to "json" or "json-line" implies that the -json flag is set.
|
||||||
|
type Format struct {
|
||||||
|
s string
|
||||||
|
SchemaVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the default value which is printed in the CLI help text.
|
||||||
|
func (f Format) String() string {
|
||||||
|
return string(f.s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is called when the user passes the flag as a command-line argument.
|
||||||
|
func (f *Format) Set(s string) error {
|
||||||
|
f.s = s
|
||||||
|
isJSON := f.isJSON()
|
||||||
|
f.IsSet = isJSON
|
||||||
|
if !isJSON {
|
||||||
|
f.Version = 0
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBoolFlag reports that this [flag.Value] can be set without an argument.
|
||||||
|
// This is the magic interface that makes -name equivalent to -name=true
|
||||||
|
// rather than using the next command-line argument.
|
||||||
|
func (f *Format) IsBoolFlag() bool {
|
||||||
|
return false // requires argument, overrides [SchemaVersion.IsBoolFlag]
|
||||||
|
}
|
||||||
|
|
||||||
|
// isJSON reports whether f represents a format based on JSON.
|
||||||
|
func (f *Format) isJSON() bool {
|
||||||
|
return f.s == "json" || f.s == "json-line"
|
||||||
|
}
|
||||||
|
|
||||||
|
// setJSON either sets or clears the format value if it doesn’t match x.
|
||||||
|
func (f *Format) setJSON(x bool) {
|
||||||
|
switch x {
|
||||||
|
case f.isJSON():
|
||||||
|
return // no change
|
||||||
|
case true:
|
||||||
|
f.s = "json"
|
||||||
|
case false:
|
||||||
|
f.s = "" // clear
|
||||||
|
f.Version = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONBool returns a [flag.Value] for a boolean -json flag
|
||||||
|
// which is aware of the underlying format.
|
||||||
|
func (f *Format) JSONBool() flag.Value {
|
||||||
|
return &formatBool{f}
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatBool struct {
|
||||||
|
*Format
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the default value which is printed in the CLI help text.
|
||||||
|
func (f formatBool) String() string {
|
||||||
|
return strconv.FormatBool(f.isJSON())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is called when the user passes the flag as a command-line argument.
|
||||||
|
func (f *formatBool) Set(s string) error {
|
||||||
|
if _, err := strconv.ParseBool(s); err != nil {
|
||||||
|
return errors.New("parse error")
|
||||||
|
}
|
||||||
|
f.SchemaVersion.Set(s)
|
||||||
|
f.setJSON(f.IsSet)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBoolFlag reports that this [flag.Value] can be set without an argument.
|
||||||
|
// This is the magic interface that makes -name equivalent to -name=true
|
||||||
|
// rather than using the next command-line argument.
|
||||||
|
func (f *formatBool) IsBoolFlag() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSONSchemaVersion returns a [flag.Value] for a [SchemaVersion] -json flag
|
||||||
|
// which is aware of the underlying format.
|
||||||
|
func (f *Format) JSONSchemaVersion() flag.Value {
|
||||||
|
return &formatSchemaVersion{f}
|
||||||
|
}
|
||||||
|
|
||||||
|
type formatSchemaVersion struct {
|
||||||
|
*Format
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns the default value which is printed in the CLI help text.
|
||||||
|
func (f formatSchemaVersion) String() string {
|
||||||
|
return f.SchemaVersion.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set is called when the user passes the flag as a command-line argument.
|
||||||
|
func (f *formatSchemaVersion) Set(s string) error {
|
||||||
|
if err := f.SchemaVersion.Set(s); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
f.setJSON(f.IsSet)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsBoolFlag reports that this [flag.Value] can be set without an argument.
|
||||||
|
// This is the magic interface that makes -name equivalent to -name=true
|
||||||
|
// rather than using the next command-line argument.
|
||||||
|
func (f *formatSchemaVersion) IsBoolFlag() bool {
|
||||||
|
return f.SchemaVersion.IsBoolFlag()
|
||||||
|
}
|
||||||
@@ -0,0 +1,404 @@
|
|||||||
|
// Copyright (c) Tailscale Inc & contributors
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package jsonoutput_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"tailscale.com/cmd/tailscale/cli/jsonoutput"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestFormat(t *testing.T) {
|
||||||
|
for _, tc := range []struct {
|
||||||
|
name string
|
||||||
|
flags []string
|
||||||
|
wantString string
|
||||||
|
wantIsSet bool
|
||||||
|
wantVersion int
|
||||||
|
wantErr string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "no-flags",
|
||||||
|
flags: []string{},
|
||||||
|
wantString: "",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json",
|
||||||
|
flags: []string{"-format=json"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-line",
|
||||||
|
flags: []string{"-format=json-line"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-not-json",
|
||||||
|
flags: []string{"-format=xml"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-bare",
|
||||||
|
flags: []string{"-bool"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-true",
|
||||||
|
flags: []string{"-bool=true"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-false",
|
||||||
|
flags: []string{"-bool=false"},
|
||||||
|
wantString: "",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-invalid",
|
||||||
|
flags: []string{"-bool=2"},
|
||||||
|
wantErr: "parse error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-bare",
|
||||||
|
flags: []string{"-version"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-true",
|
||||||
|
flags: []string{"-version=true"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-false",
|
||||||
|
flags: []string{"-version=false"},
|
||||||
|
wantString: "",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-number",
|
||||||
|
flags: []string{"-version=2"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-zero",
|
||||||
|
flags: []string{"-version=0"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-invalid",
|
||||||
|
flags: []string{"-version=1.0"},
|
||||||
|
wantErr: "parse error",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-xml-then-bool-true",
|
||||||
|
flags: []string{"-format=xml", "-bool"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-then-bool-true",
|
||||||
|
flags: []string{"-format=json", "-bool"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-line-then-bool-true",
|
||||||
|
flags: []string{"-format=json-line", "-bool"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-xml-then-bool-false",
|
||||||
|
flags: []string{"-format=xml", "-bool=false"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-then-bool-false",
|
||||||
|
flags: []string{"-format=json", "-bool=false"},
|
||||||
|
wantString: "",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-line-then-bool-false",
|
||||||
|
flags: []string{"-format=json-line", "-bool=false"},
|
||||||
|
wantString: "",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-true-then-format-xml",
|
||||||
|
flags: []string{"-bool", "-format=xml"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-true-then-format-json",
|
||||||
|
flags: []string{"-bool", "--format=json"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-true-then-format-json-line",
|
||||||
|
flags: []string{"-bool", "--format=json-line"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-false-then-format-xml",
|
||||||
|
flags: []string{"-bool=false", "--format=xml"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-false-then-format-json",
|
||||||
|
flags: []string{"-bool=false", "--format=json"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bool-false-then-format-json-line",
|
||||||
|
flags: []string{"-bool=false", "--format=json-line"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-xml-then-version-true",
|
||||||
|
flags: []string{"-format=xml", "-version"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-then-version-true",
|
||||||
|
flags: []string{"-format=json", "-version"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-line-then-version-true",
|
||||||
|
flags: []string{"-format=json-line", "-version"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-xml-then-version-number",
|
||||||
|
flags: []string{"-format=xml", "-version=2"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-then-version-number",
|
||||||
|
flags: []string{"-format=json", "-version=2"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-line-then-version-number",
|
||||||
|
flags: []string{"-format=json-line", "-version=2"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-xml-then-version-zero",
|
||||||
|
flags: []string{"-format=xml", "-version=0"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-then-version-zero",
|
||||||
|
flags: []string{"-format=json", "-version=0"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-line-then-version-zero",
|
||||||
|
flags: []string{"-format=json-line", "-version=0"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-xml-then-version-false",
|
||||||
|
flags: []string{"-format=xml", "-version=false"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-then-version-false",
|
||||||
|
flags: []string{"-format=json", "-version=false"},
|
||||||
|
wantString: "",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "format-json-line-then-version-false",
|
||||||
|
flags: []string{"-format=json-line", "-version=false"},
|
||||||
|
wantString: "",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-true-then-format-xml",
|
||||||
|
flags: []string{"-version", "-format=xml"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-true-then-format-json",
|
||||||
|
flags: []string{"-version", "--format=json"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-true-then-format-json-line",
|
||||||
|
flags: []string{"-version", "--format=json-line"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-false-then-format-xml",
|
||||||
|
flags: []string{"-version=false", "--format=xml"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-false-then-format-json",
|
||||||
|
flags: []string{"-version=false", "--format=json"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-false-then-format-json-line",
|
||||||
|
flags: []string{"-version=false", "--format=json-line"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-number-then-format-xml",
|
||||||
|
flags: []string{"-version=2", "--format=xml"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-number-then-format-json",
|
||||||
|
flags: []string{"-version=2", "--format=json"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-number-then-format-json-line",
|
||||||
|
flags: []string{"-version=2", "--format=json-line"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-zero-then-format-xml",
|
||||||
|
flags: []string{"-version=0", "--format=xml"},
|
||||||
|
wantString: "xml",
|
||||||
|
wantIsSet: false,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-zero-then-format-json",
|
||||||
|
flags: []string{"-version=0", "--format=json"},
|
||||||
|
wantString: "json",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "version-zero-then-format-json-line",
|
||||||
|
flags: []string{"-version=0", "--format=json-line"},
|
||||||
|
wantString: "json-line",
|
||||||
|
wantIsSet: true,
|
||||||
|
wantVersion: 0,
|
||||||
|
},
|
||||||
|
} {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
var f jsonoutput.Format
|
||||||
|
fs := flag.NewFlagSet("", flag.ContinueOnError)
|
||||||
|
fs.Var(&f, "format", "")
|
||||||
|
fs.Var(f.JSONBool(), "bool", "")
|
||||||
|
fs.Var(f.JSONSchemaVersion(), "version", "")
|
||||||
|
fs.SetOutput(io.Discard) // silence
|
||||||
|
|
||||||
|
t.Logf("flags: %q", tc.flags)
|
||||||
|
if err := fs.Parse(tc.flags); err != nil {
|
||||||
|
// Unwrap the header added by FlagSet.failf:
|
||||||
|
// `invalid boolean value "invalid" for -json: `
|
||||||
|
bits := strings.SplitN(err.Error(), ": ", 2)
|
||||||
|
err := errors.New(bits[len(bits)-1])
|
||||||
|
if tc.wantErr == "" && err != nil {
|
||||||
|
t.Fatalf("err %s, want <nil>", err)
|
||||||
|
} else if tc.wantErr != "" && (err == nil || err.Error() != tc.wantErr) {
|
||||||
|
t.Fatalf("err %v, want %s", err, tc.wantErr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if got := f.String(); got != tc.wantString {
|
||||||
|
t.Errorf("Format.String %q, want %q", got, tc.wantString)
|
||||||
|
}
|
||||||
|
if got := f.IsSet; got != tc.wantIsSet {
|
||||||
|
t.Errorf("Format.IsSet %t, want %t", got, tc.wantIsSet)
|
||||||
|
}
|
||||||
|
if got := f.Version; got != tc.wantVersion {
|
||||||
|
t.Errorf("Format.Version %d, want %d", got, tc.wantVersion)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -16,6 +16,12 @@
|
|||||||
// Passing just the boolean flag will always return 1, to preserve
|
// Passing just the boolean flag will always return 1, to preserve
|
||||||
// compatibility with scripts written before we versioned our output.
|
// compatibility with scripts written before we versioned our output.
|
||||||
//
|
//
|
||||||
|
// This package provides a [Format] flag type that allows callers to specify
|
||||||
|
// which output format the command should print.
|
||||||
|
// This flag provides [Format.JSONBool] and [Format.JSONSchemaVersion] methods
|
||||||
|
// to support combining both -format=json and -format=json-line options
|
||||||
|
// with either boolean or versioned -json flags.
|
||||||
|
//
|
||||||
// This package also provides [ResponseEnvelope] which is used to provide the
|
// This package also provides [ResponseEnvelope] which is used to provide the
|
||||||
// set of fields common to all versioned JSON output.
|
// set of fields common to all versioned JSON output.
|
||||||
package jsonoutput
|
package jsonoutput
|
||||||
@@ -44,7 +50,7 @@ type SchemaVersion struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// String returns the default value which is printed in the CLI help text.
|
// String returns the default value which is printed in the CLI help text.
|
||||||
func (v *SchemaVersion) String() string {
|
func (v SchemaVersion) String() string {
|
||||||
if v.IsSet {
|
if v.IsSet {
|
||||||
return strconv.Itoa(v.Version)
|
return strconv.Itoa(v.Version)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user