snapshot of https://github.com/ory/keto.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
Some checks failed
CI / Run lints and checks (push) Failing after 47s
CI / Run tests (push) Failing after 1s
CI / Run race tests (push) Failing after 5s
CI / Build CLI docs (push) Has been skipped
CI / Run buf actions (push) Failing after 5s
CodeQL / Analyze (go) (push) Failing after 3s
CodeQL / Analyze (javascript) (push) Failing after 2s
Docker Image Scanners / scanners (push) Failing after 3s
Format / format (push) Failing after 3s
Synchronize Issue Labels / Synchronize Issue Labels (push) Has been skipped
CI / Generate release (push) Has been skipped
CI / Draft newsletter (push) Has been skipped
CI / Pending approval Slack notification (push) Has been skipped
CI / Send newsletter (push) Has been skipped
This commit is contained in:
355
oryx/cmdx/printing.go
Normal file
355
oryx/cmdx/printing.go
Normal file
@@ -0,0 +1,355 @@
|
||||
// Copyright © 2023 Ory Corp
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package cmdx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
||||
"github.com/go-openapi/jsonpointer"
|
||||
"github.com/goccy/go-yaml"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/pflag"
|
||||
"github.com/tidwall/gjson"
|
||||
)
|
||||
|
||||
type (
|
||||
TableHeader interface {
|
||||
Header() []string
|
||||
}
|
||||
TableRow interface {
|
||||
TableHeader
|
||||
Columns() []string
|
||||
Interface() interface{}
|
||||
}
|
||||
Table interface {
|
||||
TableHeader
|
||||
Table() [][]string
|
||||
Interface() interface{}
|
||||
Len() int
|
||||
}
|
||||
Nil struct{}
|
||||
|
||||
Format string
|
||||
)
|
||||
|
||||
const (
|
||||
FormatQuiet Format = "quiet"
|
||||
FormatTable Format = "table"
|
||||
FormatJSON Format = "json"
|
||||
FormatJSONPath Format = "jsonpath"
|
||||
FormatJSONPointer Format = "jsonpointer"
|
||||
FormatJSONPretty Format = "json-pretty"
|
||||
FormatYAML Format = "yaml"
|
||||
FormatDefault Format = "default"
|
||||
|
||||
FlagFormat = "format"
|
||||
|
||||
None = "<none>"
|
||||
)
|
||||
|
||||
func (Nil) String() string {
|
||||
return "null"
|
||||
}
|
||||
|
||||
func (Nil) Interface() interface{} {
|
||||
return nil
|
||||
}
|
||||
|
||||
type printOptions struct {
|
||||
format string
|
||||
}
|
||||
|
||||
type PrintOption func(*printOptions)
|
||||
|
||||
func WithFormat(v string) PrintOption {
|
||||
return func(o *printOptions) {
|
||||
o.format = v
|
||||
}
|
||||
}
|
||||
|
||||
func PrintErrors(cmd *cobra.Command, errs map[string]error) {
|
||||
for src, err := range errs {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s: %s\n", src, err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
func PrintRow(cmd *cobra.Command, row TableRow) {
|
||||
PrintRowf(cmd.OutOrStdout(), row, WithFormat(getFormatValue(cmd)))
|
||||
}
|
||||
|
||||
func PrintRowf(w io.Writer, row TableRow, opts ...PrintOption) {
|
||||
o := &printOptions{}
|
||||
for _, fn := range opts {
|
||||
fn(o)
|
||||
}
|
||||
|
||||
switch parseFormat(o.format) {
|
||||
case FormatQuiet:
|
||||
if idAble, ok := row.(interface{ ID() string }); ok {
|
||||
_, _ = fmt.Fprintln(w, idAble.ID())
|
||||
break
|
||||
}
|
||||
_, _ = fmt.Fprintln(w, row.Columns()[0])
|
||||
case FormatJSON:
|
||||
printJSON(w, row.Interface(), false, "")
|
||||
case FormatYAML:
|
||||
printYAML(w, row.Interface())
|
||||
case FormatJSONPretty:
|
||||
printJSON(w, row.Interface(), true, "")
|
||||
case FormatJSONPath:
|
||||
printJSON(w, row.Interface(), true, getPath(o.format))
|
||||
case FormatJSONPointer:
|
||||
printJSON(w, filterJSONPointer(o.format, row.Interface()), true, "")
|
||||
case FormatTable, FormatDefault:
|
||||
w := tabwriter.NewWriter(w, 0, 8, 1, '\t', 0)
|
||||
|
||||
fields := row.Columns()
|
||||
for i, h := range row.Header() {
|
||||
_, _ = fmt.Fprintf(w, "%s\t%s\t\n", h, fields[i])
|
||||
}
|
||||
|
||||
_ = w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
func filterJSONPointer(f string, data any) any {
|
||||
_, jsonptr, found := strings.Cut(f, "=")
|
||||
if !found {
|
||||
_, _ = fmt.Fprintf(os.Stderr,
|
||||
"Format %s is missing a JSON pointer, e.g., --%s=%s=<jsonpointer>. The path syntax is described at https://datatracker.ietf.org/doc/html/draft-ietf-appsawg-json-pointer-07.",
|
||||
f, FlagFormat, f)
|
||||
os.Exit(1)
|
||||
}
|
||||
ptr, err := jsonpointer.New(jsonptr)
|
||||
Must(err, "invalid JSON pointer: %s", err)
|
||||
|
||||
result, _, err := ptr.Get(data)
|
||||
Must(err, "failed to apply JSON pointer: %s", err)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
func PrintTable(cmd *cobra.Command, table Table) {
|
||||
PrintTablef(cmd.OutOrStdout(), table, WithFormat(getFormatValue(cmd)))
|
||||
}
|
||||
|
||||
func PrintTablef(w io.Writer, table Table, opts ...PrintOption) {
|
||||
o := &printOptions{}
|
||||
for _, fn := range opts {
|
||||
fn(o)
|
||||
}
|
||||
|
||||
switch parseFormat(o.format) {
|
||||
case FormatQuiet:
|
||||
if table.Len() == 0 {
|
||||
fmt.Fprintln(w)
|
||||
}
|
||||
|
||||
if idAble, ok := table.(interface{ IDs() []string }); ok {
|
||||
for _, row := range idAble.IDs() {
|
||||
fmt.Fprintln(w, row)
|
||||
}
|
||||
break
|
||||
}
|
||||
|
||||
for _, row := range table.Table() {
|
||||
fmt.Fprintln(w, row[0])
|
||||
}
|
||||
case FormatJSON:
|
||||
printJSON(w, table.Interface(), false, "")
|
||||
case FormatJSONPretty:
|
||||
printJSON(w, table.Interface(), true, "")
|
||||
case FormatJSONPath:
|
||||
printJSON(w, table.Interface(), true, getPath(o.format))
|
||||
case FormatJSONPointer:
|
||||
printJSON(w, filterJSONPointer(o.format, table.Interface()), true, "")
|
||||
case FormatYAML:
|
||||
printYAML(w, table.Interface())
|
||||
default:
|
||||
w := tabwriter.NewWriter(w, 0, 8, 1, '\t', 0)
|
||||
|
||||
for _, h := range table.Header() {
|
||||
fmt.Fprintf(w, "%s\t", h)
|
||||
}
|
||||
fmt.Fprintln(w)
|
||||
|
||||
for _, row := range table.Table() {
|
||||
fmt.Fprintln(w, strings.Join(row, "\t")+"\t")
|
||||
}
|
||||
|
||||
_ = w.Flush()
|
||||
}
|
||||
}
|
||||
|
||||
type interfacer interface{ Interface() interface{} }
|
||||
|
||||
func PrintJSONAble(cmd *cobra.Command, d interface{ String() string }) {
|
||||
PrintJSONAblef(cmd.OutOrStdout(), d, WithFormat(getFormatValue(cmd)))
|
||||
}
|
||||
|
||||
func PrintJSONAblef(w io.Writer, d interface{ String() string }, opts ...PrintOption) {
|
||||
if d == nil {
|
||||
d = Nil{}
|
||||
}
|
||||
|
||||
o := &printOptions{}
|
||||
for _, fn := range opts {
|
||||
fn(o)
|
||||
}
|
||||
|
||||
var path string
|
||||
switch parseFormat(o.format) {
|
||||
default:
|
||||
_, _ = fmt.Fprint(w, d.String())
|
||||
case FormatJSON:
|
||||
var v interface{} = d
|
||||
if i, ok := d.(interfacer); ok {
|
||||
v = i
|
||||
}
|
||||
printJSON(w, v, false, "")
|
||||
case FormatJSONPath:
|
||||
path = getPath(o.format)
|
||||
fallthrough
|
||||
case FormatJSONPretty:
|
||||
var v interface{} = d
|
||||
if i, ok := d.(interfacer); ok {
|
||||
v = i
|
||||
}
|
||||
printJSON(w, v, true, path)
|
||||
case FormatJSONPointer:
|
||||
var v interface{} = d
|
||||
if i, ok := d.(interfacer); ok {
|
||||
v = i
|
||||
}
|
||||
printJSON(w, filterJSONPointer(o.format, v), true, "")
|
||||
case FormatYAML:
|
||||
var v interface{} = d
|
||||
if i, ok := d.(interfacer); ok {
|
||||
v = i
|
||||
}
|
||||
printYAML(w, v)
|
||||
}
|
||||
}
|
||||
|
||||
func getQuiet(cmd *cobra.Command) bool {
|
||||
// ignore the error here as we use this function also when the flag might not be registered
|
||||
q, _ := cmd.Flags().GetBool(FlagQuiet)
|
||||
return q
|
||||
}
|
||||
|
||||
func getFormatValue(cmd *cobra.Command) string {
|
||||
if getQuiet(cmd) {
|
||||
return string(FormatQuiet)
|
||||
}
|
||||
|
||||
f, _ := cmd.Flags().GetString(FlagFormat)
|
||||
return f
|
||||
}
|
||||
|
||||
func parseFormat(f string) Format {
|
||||
switch {
|
||||
case f == string(FormatQuiet):
|
||||
return FormatQuiet
|
||||
case f == string(FormatTable):
|
||||
return FormatTable
|
||||
case f == string(FormatJSON):
|
||||
return FormatJSON
|
||||
case strings.HasPrefix(f, string(FormatJSONPath)):
|
||||
return FormatJSONPath
|
||||
case strings.HasPrefix(f, string(FormatJSONPointer)):
|
||||
return FormatJSONPointer
|
||||
case f == string(FormatJSONPretty):
|
||||
return FormatJSONPretty
|
||||
case f == string(FormatYAML):
|
||||
return FormatYAML
|
||||
default:
|
||||
return FormatDefault
|
||||
}
|
||||
}
|
||||
|
||||
func getPath(f string) string {
|
||||
_, path, found := strings.Cut(f, "=")
|
||||
if !found {
|
||||
_, _ = fmt.Fprintf(os.Stderr,
|
||||
"Format %s is missing a path, e.g., --%s=%s=<path>. The path syntax is described at https://github.com/tidwall/gjson/blob/master/SYNTAX.md",
|
||||
f, FlagFormat, f)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
return path
|
||||
}
|
||||
|
||||
func printJSON(w io.Writer, v interface{}, pretty bool, path string) {
|
||||
if path != "" {
|
||||
temp, err := json.Marshal(v)
|
||||
Must(err, "Error encoding JSON: %s", err)
|
||||
v = gjson.GetBytes(temp, path).Value()
|
||||
}
|
||||
|
||||
e := json.NewEncoder(w)
|
||||
if pretty {
|
||||
e.SetIndent("", " ")
|
||||
}
|
||||
err := e.Encode(v)
|
||||
// unexpected error
|
||||
Must(err, "Error encoding JSON: %s", err)
|
||||
}
|
||||
|
||||
func printYAML(w io.Writer, v interface{}) {
|
||||
j, err := json.Marshal(v)
|
||||
Must(err, "Error encoding JSON: %s", err)
|
||||
e, err := yaml.JSONToYAML(j)
|
||||
Must(err, "Error encoding YAML: %s", err)
|
||||
_, _ = w.Write(e)
|
||||
}
|
||||
|
||||
func RegisterJSONFormatFlags(flags *pflag.FlagSet) {
|
||||
flags.String(FlagFormat, string(FormatDefault), fmt.Sprintf("Set the output format. One of %s, %s, %s, %s, %s and %s.", FormatDefault, FormatJSON, FormatYAML, FormatJSONPretty, FormatJSONPath, FormatJSONPointer))
|
||||
}
|
||||
|
||||
func RegisterFormatFlags(flags *pflag.FlagSet) {
|
||||
RegisterNoiseFlags(flags)
|
||||
flags.String(FlagFormat, string(FormatTable), fmt.Sprintf("Set the output format. One of %s, %s, %s, %s, %s and %s.", FormatTable, FormatJSON, FormatYAML, FormatJSONPretty, FormatJSONPath, FormatJSONPointer))
|
||||
}
|
||||
|
||||
func PrintOpenAPIError(cmd *cobra.Command, err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var be interface {
|
||||
Body() []byte
|
||||
}
|
||||
if !errors.As(err, &be) {
|
||||
return err
|
||||
}
|
||||
|
||||
body := be.Body()
|
||||
didPrettyPrint := false
|
||||
if message := gjson.GetBytes(body, "error.message"); message.Exists() {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\n", message.String())
|
||||
didPrettyPrint = true
|
||||
}
|
||||
if reason := gjson.GetBytes(body, "error.reason"); reason.Exists() {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\n", reason.String())
|
||||
didPrettyPrint = true
|
||||
}
|
||||
|
||||
if didPrettyPrint {
|
||||
return FailSilently(cmd)
|
||||
}
|
||||
|
||||
if body, err := json.MarshalIndent(json.RawMessage(body), "", " "); err == nil {
|
||||
_, _ = fmt.Fprintf(cmd.ErrOrStderr(), "%s\nFailed to execute API request, see error above.\n", body)
|
||||
return FailSilently(cmd)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
Reference in New Issue
Block a user