diff --git a/cmd/yorkie/commands.go b/cmd/yorkie/commands.go index 7e5418daa..0ea295900 100644 --- a/cmd/yorkie/commands.go +++ b/cmd/yorkie/commands.go @@ -18,6 +18,7 @@ package main import ( + "errors" "os" "path" @@ -55,4 +56,20 @@ func init() { rootCmd.PersistentFlags().String("rpc-addr", "localhost:8080", "Address of the rpc server") _ = viper.BindPFlag("rpcAddr", rootCmd.PersistentFlags().Lookup("rpc-addr")) + + rootCmd.PersistentFlags().StringP("output", "o", "", "One of 'yaml' or 'json'.") + _ = viper.BindPFlag("output", rootCmd.PersistentFlags().Lookup("output")) + + rootCmd.PersistentPreRunE = func(cmd *cobra.Command, args []string) error { + return validateOutputOpts() + } +} + +// validateOutputOpts validates the output options. +func validateOutputOpts() error { + output := viper.GetString("output") + if output != DefaultOutput && output != YamlOutput && output != JSONOutput { + return errors.New(`--output must be 'yaml' or 'json'`) + } + return nil } diff --git a/cmd/yorkie/context/list.go b/cmd/yorkie/context/list.go index e06387996..db3e4a24c 100644 --- a/cmd/yorkie/context/list.go +++ b/cmd/yorkie/context/list.go @@ -17,15 +17,24 @@ package context import ( + "encoding/json" "fmt" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" "github.com/spf13/viper" + "gopkg.in/yaml.v3" "github.com/yorkie-team/yorkie/cmd/yorkie/config" ) +type contextInfo struct { + Current string `json:"current" yaml:"current"` + RPCAddr string `json:"rpc_addr" yaml:"rpc_addr"` + Insecure string `json:"insecure" yaml:"insecure"` + Token string `json:"token" yaml:"token"` +} + func newListCommand() *cobra.Command { return &cobra.Command{ Use: "ls", @@ -37,14 +46,7 @@ func newListCommand() *cobra.Command { return err } - tw := table.NewWriter() - tw.Style().Options.DrawBorder = false - tw.Style().Options.SeparateColumns = false - tw.Style().Options.SeparateFooter = false - tw.Style().Options.SeparateHeader = false - tw.Style().Options.SeparateRows = false - - tw.AppendHeader(table.Row{"CURRENT", "RPC ADDR", "INSECURE", "TOKEN"}) + contexts := make([]contextInfo, 0, len(conf.Auths)) for rpcAddr, auth := range conf.Auths { current := "" if rpcAddr == viper.GetString("rpcAddr") { @@ -61,16 +63,58 @@ func newListCommand() *cobra.Command { ellipsisToken = auth.Token[:10] + "..." + auth.Token[len(auth.Token)-10:] } - tw.AppendRow(table.Row{current, rpcAddr, insecure, ellipsisToken}) + contexts = append(contexts, contextInfo{ + Current: current, + RPCAddr: rpcAddr, + Insecure: insecure, + Token: ellipsisToken, + }) } - fmt.Println(tw.Render()) + output := viper.GetString("output") + if err := printContexts(cmd, output, contexts); err != nil { + return err + } return nil }, } } +func printContexts(cmd *cobra.Command, output string, contexts []contextInfo) error { + switch output { + case "": + tw := table.NewWriter() + tw.Style().Options.DrawBorder = false + tw.Style().Options.SeparateColumns = false + tw.Style().Options.SeparateFooter = false + tw.Style().Options.SeparateHeader = false + tw.Style().Options.SeparateRows = false + + tw.AppendHeader(table.Row{"CURRENT", "RPC ADDR", "INSECURE", "TOKEN"}) + for _, ctx := range contexts { + tw.AppendRow(table.Row{ctx.Current, ctx.RPCAddr, ctx.Insecure, ctx.Token}) + } + cmd.Println(tw.Render()) + case "json": + marshalled, err := json.MarshalIndent(contexts, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + cmd.Println(string(marshalled)) + case "yaml": + marshalled, err := yaml.Marshal(contexts) + if err != nil { + return fmt.Errorf("failed to marshal YAML: %w", err) + } + cmd.Println(string(marshalled)) + default: + return fmt.Errorf("unknown output format: %s", output) + } + + return nil +} + func init() { SubCmd.AddCommand(newListCommand()) } diff --git a/cmd/yorkie/document/list.go b/cmd/yorkie/document/list.go index 71d886e87..adb3caa90 100644 --- a/cmd/yorkie/document/list.go +++ b/cmd/yorkie/document/list.go @@ -18,14 +18,18 @@ package document import ( "context" + "encoding/json" "errors" + "fmt" "time" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" "github.com/spf13/viper" + "gopkg.in/yaml.v3" "github.com/yorkie-team/yorkie/admin" + "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/cmd/yorkie/config" "github.com/yorkie-team/yorkie/pkg/units" ) @@ -68,36 +72,63 @@ func newListCommand() *cobra.Command { return err } - tw := table.NewWriter() - tw.Style().Options.DrawBorder = false - tw.Style().Options.SeparateColumns = false - tw.Style().Options.SeparateFooter = false - tw.Style().Options.SeparateHeader = false - tw.Style().Options.SeparateRows = false - tw.AppendHeader(table.Row{ - "ID", - "KEY", - "CREATED AT", - "ACCESSED AT", - "UPDATED AT", - "SNAPSHOT", - }) - for _, document := range documents { - tw.AppendRow(table.Row{ - document.ID, - document.Key, - units.HumanDuration(time.Now().UTC().Sub(document.CreatedAt)), - units.HumanDuration(time.Now().UTC().Sub(document.AccessedAt)), - units.HumanDuration(time.Now().UTC().Sub(document.UpdatedAt)), - document.Snapshot, - }) + output := viper.GetString("output") + if err := printDocuments(cmd, output, documents); err != nil { + return err } - cmd.Printf("%s\n", tw.Render()) + return nil }, } } +func printDocuments(cmd *cobra.Command, output string, documents []*types.DocumentSummary) error { + switch output { + case "": + tw := table.NewWriter() + tw.Style().Options.DrawBorder = false + tw.Style().Options.SeparateColumns = false + tw.Style().Options.SeparateFooter = false + tw.Style().Options.SeparateHeader = false + tw.Style().Options.SeparateRows = false + tw.AppendHeader(table.Row{ + "ID", + "KEY", + "CREATED AT", + "ACCESSED AT", + "UPDATED AT", + "SNAPSHOT", + }) + for _, document := range documents { + tw.AppendRow(table.Row{ + document.ID, + document.Key, + units.HumanDuration(time.Now().UTC().Sub(document.CreatedAt)), + units.HumanDuration(time.Now().UTC().Sub(document.AccessedAt)), + units.HumanDuration(time.Now().UTC().Sub(document.UpdatedAt)), + document.Snapshot, + }) + } + cmd.Printf("%s\n", tw.Render()) + case "json": + jsonOutput, err := json.MarshalIndent(documents, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + cmd.Println(string(jsonOutput)) + case "yaml": + yamlOutput, err := yaml.Marshal(documents) + if err != nil { + return fmt.Errorf("failed to marshal YAML: %w", err) + } + cmd.Println(string(yamlOutput)) + default: + return fmt.Errorf("unknown output format: %s", output) + } + + return nil +} + func init() { cmd := newListCommand() cmd.Flags().StringVar( diff --git a/cmd/yorkie/history.go b/cmd/yorkie/history.go index 73782bbe2..64387b6f2 100644 --- a/cmd/yorkie/history.go +++ b/cmd/yorkie/history.go @@ -18,13 +18,17 @@ package main import ( "context" + "encoding/json" "errors" + "fmt" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" "github.com/spf13/viper" + "gopkg.in/yaml.v3" "github.com/yorkie-team/yorkie/admin" + "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/cmd/yorkie/config" "github.com/yorkie-team/yorkie/pkg/document/key" ) @@ -44,7 +48,6 @@ func newHistoryCmd() *cobra.Command { if len(args) != 2 { return errors.New("project name and document key are required") } - rpcAddr := viper.GetString("rpcAddr") auth, err := config.LoadAuth(rpcAddr) if err != nil { @@ -72,30 +75,57 @@ func newHistoryCmd() *cobra.Command { return err } - tw := table.NewWriter() - tw.Style().Options.DrawBorder = false - tw.Style().Options.SeparateColumns = false - tw.Style().Options.SeparateFooter = false - tw.Style().Options.SeparateHeader = false - tw.Style().Options.SeparateRows = false - tw.AppendHeader(table.Row{ - "SEQ", - "MESSAGE", - "SNAPSHOT", - }) - for _, change := range changes { - tw.AppendRow(table.Row{ - change.ID.ServerSeq(), - change.Message, - change.Snapshot, - }) + output := viper.GetString("output") + if err := printHistories(cmd, output, changes); err != nil { + return err } - cmd.Printf("%s\n", tw.Render()) + return nil }, } } +func printHistories(cmd *cobra.Command, output string, changes []*types.ChangeSummary) error { + switch output { + case DefaultOutput: + tw := table.NewWriter() + tw.Style().Options.DrawBorder = false + tw.Style().Options.SeparateColumns = false + tw.Style().Options.SeparateFooter = false + tw.Style().Options.SeparateHeader = false + tw.Style().Options.SeparateRows = false + tw.AppendHeader(table.Row{ + "SEQ", + "MESSAGE", + "SNAPSHOT", + }) + for _, change := range changes { + tw.AppendRow(table.Row{ + change.ID.ServerSeq(), + change.Message, + change.Snapshot, + }) + } + cmd.Printf("%s\n", tw.Render()) + case JSONOutput: + jsonOutput, err := json.MarshalIndent(changes, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + cmd.Println(string(jsonOutput)) + case YamlOutput: + yamlOutput, err := yaml.Marshal(changes) + if err != nil { + return fmt.Errorf("failed to marshal YAML: %w", err) + } + cmd.Println(string(yamlOutput)) + default: + return fmt.Errorf("unknown output format: %s", output) + } + + return nil +} + func init() { cmd := newHistoryCmd() cmd.Flags().Int64Var( diff --git a/cmd/yorkie/output.go b/cmd/yorkie/output.go new file mode 100644 index 000000000..8b5f3a3f8 --- /dev/null +++ b/cmd/yorkie/output.go @@ -0,0 +1,7 @@ +package main + +const ( + DefaultOutput = "" // DefaultOutput is for table format + YamlOutput = "yaml" // YamlOutput is for yaml format + JSONOutput = "json" // JSONOutput is for json format +) diff --git a/cmd/yorkie/project/create.go b/cmd/yorkie/project/create.go index 5e1f0416b..36756ef96 100644 --- a/cmd/yorkie/project/create.go +++ b/cmd/yorkie/project/create.go @@ -26,8 +26,10 @@ import ( "github.com/spf13/viper" "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/status" + "gopkg.in/yaml.v3" "github.com/yorkie-team/yorkie/admin" + "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/cmd/yorkie/config" ) @@ -72,18 +74,37 @@ func newCreateCommand() *cobra.Command { return err } - encoded, err := json.Marshal(project) - if err != nil { - return fmt.Errorf("marshal project: %w", err) + output := viper.GetString("output") + if err := printCreateProjectInfo(cmd, output, project); err != nil { + return err } - cmd.Println(string(encoded)) - return nil }, } } +func printCreateProjectInfo(cmd *cobra.Command, output string, project *types.Project) error { + switch output { + case JSONOutput, DefaultOutput: + encoded, err := json.Marshal(project) + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + cmd.Println(string(encoded)) + case YamlOutput: + marshalled, err := yaml.Marshal(project) + if err != nil { + return fmt.Errorf("failed to marshal YAML: %w", err) + } + cmd.Println(string(marshalled)) + default: + return fmt.Errorf("unknown output format: %s", output) + } + + return nil +} + func init() { SubCmd.AddCommand(newCreateCommand()) } diff --git a/cmd/yorkie/project/list.go b/cmd/yorkie/project/list.go index 14a9ecbaf..4c035e6b8 100644 --- a/cmd/yorkie/project/list.go +++ b/cmd/yorkie/project/list.go @@ -18,13 +18,17 @@ package project import ( "context" + "encoding/json" + "fmt" "time" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" "github.com/spf13/viper" + "gopkg.in/yaml.v3" "github.com/yorkie-team/yorkie/admin" + "github.com/yorkie-team/yorkie/api/types" "github.com/yorkie-team/yorkie/cmd/yorkie/config" "github.com/yorkie-team/yorkie/pkg/units" ) @@ -36,6 +40,7 @@ func newListCommand() *cobra.Command { PreRunE: config.Preload, RunE: func(cmd *cobra.Command, args []string) error { rpcAddr := viper.GetString("rpcAddr") + auth, err := config.LoadAuth(rpcAddr) if err != nil { return err @@ -55,39 +60,66 @@ func newListCommand() *cobra.Command { return err } - tw := table.NewWriter() - tw.Style().Options.DrawBorder = false - tw.Style().Options.SeparateColumns = false - tw.Style().Options.SeparateFooter = false - tw.Style().Options.SeparateHeader = false - tw.Style().Options.SeparateRows = false - tw.AppendHeader(table.Row{ - "NAME", - "PUBLIC KEY", - "SECRET KEY", - "AUTH WEBHOOK URL", - "AUTH WEBHOOK METHODS", - "CLIENT DEACTIVATE THRESHOLD", - "CREATED AT", - }) - for _, project := range projects { - tw.AppendRow(table.Row{ - project.Name, - project.PublicKey, - project.SecretKey, - project.AuthWebhookURL, - project.AuthWebhookMethods, - project.ClientDeactivateThreshold, - units.HumanDuration(time.Now().UTC().Sub(project.CreatedAt)), - }) + output := viper.GetString("output") + err2 := printProjects(cmd, output, projects) + if err2 != nil { + return err2 } - cmd.Printf("%s\n", tw.Render()) return nil }, } } +func printProjects(cmd *cobra.Command, output string, projects []*types.Project) error { + switch output { + case DefaultOutput: + tw := table.NewWriter() + tw.Style().Options.DrawBorder = false + tw.Style().Options.SeparateColumns = false + tw.Style().Options.SeparateFooter = false + tw.Style().Options.SeparateHeader = false + tw.Style().Options.SeparateRows = false + tw.AppendHeader(table.Row{ + "NAME", + "PUBLIC KEY", + "SECRET KEY", + "AUTH WEBHOOK URL", + "AUTH WEBHOOK METHODS", + "CLIENT DEACTIVATE THRESHOLD", + "CREATED AT", + }) + for _, project := range projects { + tw.AppendRow(table.Row{ + project.Name, + project.PublicKey, + project.SecretKey, + project.AuthWebhookURL, + project.AuthWebhookMethods, + project.ClientDeactivateThreshold, + units.HumanDuration(time.Now().UTC().Sub(project.CreatedAt)), + }) + } + cmd.Println(tw.Render()) + case JSONOutput: + jsonOutput, err := json.MarshalIndent(projects, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + cmd.Println(string(jsonOutput)) + case YamlOutput: + yamlOutput, err := yaml.Marshal(projects) + if err != nil { + return fmt.Errorf("failed to marshal YAML: %w", err) + } + cmd.Println(string(yamlOutput)) + default: + return fmt.Errorf("unknown output format: %s", output) + } + + return nil +} + func init() { SubCmd.AddCommand(newListCommand()) } diff --git a/cmd/yorkie/project/project.go b/cmd/yorkie/project/project.go index 4e45ed7fa..9eec11816 100644 --- a/cmd/yorkie/project/project.go +++ b/cmd/yorkie/project/project.go @@ -26,3 +26,9 @@ var ( Short: "Manage projects", } ) + +const ( + DefaultOutput = "" // DefaultOutput is for table format + YamlOutput = "yaml" // YamlOutput is for yaml format + JSONOutput = "json" // JSONOutput is for json format +) diff --git a/cmd/yorkie/project/update.go b/cmd/yorkie/project/update.go index c26eb94be..b80b53abf 100644 --- a/cmd/yorkie/project/update.go +++ b/cmd/yorkie/project/update.go @@ -26,6 +26,7 @@ import ( "github.com/spf13/viper" "google.golang.org/genproto/googleapis/rpc/errdetails" "google.golang.org/grpc/status" + "gopkg.in/yaml.v3" "github.com/yorkie-team/yorkie/admin" "github.com/yorkie-team/yorkie/api/types" @@ -48,7 +49,6 @@ func newUpdateCommand() *cobra.Command { if len(args) != 1 { return errors.New("name is required") } - name := args[0] rpcAddr := viper.GetString("rpcAddr") @@ -109,18 +109,37 @@ func newUpdateCommand() *cobra.Command { return err } - encoded, err := json.Marshal(updated) - if err != nil { - return fmt.Errorf("marshal project: %w", err) + output := viper.GetString("output") + if err := printUpdateProjectInfo(cmd, output, updated); err != nil { + return err } - cmd.Println(string(encoded)) - return nil }, } } +func printUpdateProjectInfo(cmd *cobra.Command, output string, project *types.Project) error { + switch output { + case JSONOutput, DefaultOutput: + encoded, err := json.Marshal(project) + if err != nil { + return fmt.Errorf("failed to marshal JSON: %w", err) + } + cmd.Println(string(encoded)) + case YamlOutput: + encoded, err := yaml.Marshal(project) + if err != nil { + return fmt.Errorf("failed to marshal YAML: %w", err) + } + cmd.Println(string(encoded)) + default: + return fmt.Errorf("unknown output format: %s", output) + } + + return nil +} + func init() { cmd := newUpdateCommand() cmd.Flags().StringVar( diff --git a/cmd/yorkie/version.go b/cmd/yorkie/version.go index 7207f76db..1bfaa1967 100644 --- a/cmd/yorkie/version.go +++ b/cmd/yorkie/version.go @@ -20,6 +20,7 @@ import ( "context" "encoding/json" "errors" + "fmt" "runtime" "connectrpc.com/connect" @@ -44,10 +45,6 @@ func newVersionCmd() *cobra.Command { Short: "Print the version number of Yorkie", PreRunE: config.Preload, RunE: func(cmd *cobra.Command, args []string) error { - if err := validateOutputOpts(); err != nil { - return err - } - info := types.VersionInfo{ ClientVersion: clientVersion(), } @@ -57,6 +54,7 @@ func newVersionCmd() *cobra.Command { info.ServerVersion, serverErr = fetchServerVersion() } + output := viper.GetString("output") if err := printVersionInfo(cmd, output, &info); err != nil { return err } @@ -119,7 +117,7 @@ func printServerError(cmd *cobra.Command, err error) { func printVersionInfo(cmd *cobra.Command, output string, versionInfo *types.VersionInfo) error { switch output { - case "": + case DefaultOutput: cmd.Printf("Yorkie Client: %s\n", versionInfo.ClientVersion.YorkieVersion) cmd.Printf("Go: %s\n", versionInfo.ClientVersion.GoVersion) cmd.Printf("Build Date: %s\n", versionInfo.ClientVersion.BuildDate) @@ -128,29 +126,20 @@ func printVersionInfo(cmd *cobra.Command, output string, versionInfo *types.Vers cmd.Printf("Go: %s\n", versionInfo.ServerVersion.GoVersion) cmd.Printf("Build Date: %s\n", versionInfo.ServerVersion.BuildDate) } - case "yaml": + case YamlOutput: marshalled, err := yaml.Marshal(versionInfo) if err != nil { - return errors.New("marshal YAML") + return fmt.Errorf("failed to marshal YAML: %w", err) } cmd.Println(string(marshalled)) - case "json": + case JSONOutput: marshalled, err := json.MarshalIndent(versionInfo, "", " ") if err != nil { - return errors.New("marshal JSON") + return fmt.Errorf("failed to marshal JSON: %w", err) } cmd.Println(string(marshalled)) default: - return errors.New("unknown output format") - } - - return nil -} - -// validateOutputOpts validates the output options. -func validateOutputOpts() error { - if output != "" && output != "yaml" && output != "json" { - return errors.New(`--output must be 'yaml' or 'json'`) + return fmt.Errorf("unknown output format: %s", output) } return nil @@ -166,16 +155,5 @@ func init() { "Shows client version only (no server required).", ) - // TODO(hackerwins): Output format should be configurable globally. - // So, we need to move this to the root command like `--rpc-addr` and - // apply it to all subcommands that print output. - cmd.Flags().StringVarP( - &output, - "output", - "o", - output, - "One of 'yaml' or 'json'.", - ) - rootCmd.AddCommand(cmd) }