Skip to content

Commit

Permalink
add list cli command (#235)
Browse files Browse the repository at this point in the history
  • Loading branch information
ktr0731 authored Feb 11, 2020
1 parent 277a4cb commit 27410ec
Show file tree
Hide file tree
Showing 45 changed files with 920 additions and 342 deletions.
1 change: 1 addition & 0 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ func (a *App) Run(args []string) int {
case "cli", "repl": // Sub commands for new-style interface.
// If an arg named "cli" or "repl" is passed, it is regarded as a sub-command of new-style.
a.cmd.registerNewCommands()
a.cmd.RunE = nil
case "-h", "--help":
// If the help flags is passed, call registerNewCommands for display sub-command helps.
a.cmd.registerNewCommands()
Expand Down
51 changes: 49 additions & 2 deletions app/cli_commands.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package app

import (
"strings"

"github.com/ktr0731/evans/cui"
"github.com/ktr0731/evans/mode"
"github.com/pkg/errors"
Expand All @@ -12,12 +14,55 @@ func newCLICallCommand(flags *flags, ui cui.UI) *cobra.Command {
Use: "call [options ...] <method>",
Aliases: []string{"c"},
Short: "call a RPC",
Long: `call invokes a RPC based on the passed method name.`,
RunE: runFunc(flags, func(cmd *cobra.Command, cfg *mergedConfig) error {
args := cmd.Flags().Args()
if len(args) == 0 {
return errors.New("method is required")
}
if err := mode.RunAsCLIMode(cfg.Config, args[0], cfg.file, ui); err != nil {
invoker, err := mode.NewCallCLIInvoker(ui, args[0], cfg.file, cfg.Config.Request.Header)
if err != nil {
return err
}
if err := mode.RunAsCLIMode(cfg.Config, invoker); err != nil {
return errors.Wrap(err, "failed to run CLI mode")
}
return nil
}),
SilenceErrors: true,
SilenceUsage: true,
}

bindCLICallFlags(cmd.Flags(), flags, ui.Writer())

cmd.SetHelpFunc(usageFunc(ui.Writer()))
return cmd
}

func newCLIListCommand(flags *flags, ui cui.UI) *cobra.Command {
var (
out string
)
cmd := &cobra.Command{
Use: "list [options ...] [fully-qualified service/method name]",
Aliases: []string{"ls", "show"},
Short: "list services or methods",
Long: `list provides listing feature against to gRPC services or methods belong to a service.
If a fully-qualified service name (in the form of <package name>.<service name>),
list lists method names belong to the service. If not, list lists all services.`,
Example: strings.Join([]string{
" $ evans -r cli list # list all services",
" $ evans -r cli list -o json # list all services with JSON format",
` $ evans -r cli list api.Service # list all methods belong to service "api.Service"`,
}, "\n"),
RunE: runFunc(flags, func(cmd *cobra.Command, cfg *mergedConfig) error {
var dsn string
args := cmd.Flags().Args()
if len(args) > 0 {
dsn = args[0]
}
invoker := mode.NewListCLIInvoker(ui, dsn, out)
if err := mode.RunAsCLIMode(cfg.Config, invoker); err != nil {
return errors.Wrap(err, "failed to run CLI mode")
}
return nil
Expand All @@ -26,7 +71,9 @@ func newCLICallCommand(flags *flags, ui cui.UI) *cobra.Command {
SilenceUsage: true,
}

bindCLIFlags(cmd.LocalFlags(), flags, ui.Writer())
f := cmd.Flags()
initFlagSet(f, ui.Writer())
f.StringVarP(&out, "output", "o", "name", `output format. one of "json" or "name".`)

cmd.SetHelpFunc(usageFunc(ui.Writer()))
return cmd
Expand Down
156 changes: 83 additions & 73 deletions app/commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,47 +98,26 @@ func runFunc(
func newOldCommand(flags *flags, ui cui.UI) *command {
cmd := &cobra.Command{
Use: "evans [global options ...] <command>",
RunE: runFunc(flags, func(cmd *cobra.Command, cfg *mergedConfig) error {
RunE: runFunc(flags, func(cmd *cobra.Command, cfg *mergedConfig) (err error) {
if cfg.REPL.ColoredOutput {
ui = cui.NewColored(ui)
}

defer ui.Warn("evans: deprecated usage, please use sub-commands. see `evans -h` for more details.")
defer func() {
if err == nil {
ui.Warn("evans: deprecated usage, please use sub-commands. see `evans -h` for more details.")
}
}()

isCLIMode := (cfg.cli || mode.IsCLIMode(cfg.file))
if cfg.repl || !isCLIMode {
cache, err := cache.Get()
if err != nil {
return errors.Wrap(err, "failed to get the cache content")
}

baseCtx, cancel := context.WithCancel(context.Background())
defer cancel()
eg, ctx := errgroup.WithContext(baseCtx)
// Run update checker asynchronously.
eg.Go(func() error {
return checkUpdate(ctx, cfg.Config, cache)
})

if cfg.Config.Meta.AutoUpdate {
eg.Go(func() error {
return processUpdate(ctx, cfg.Config, ui.Writer(), cache, prompt.New())
})
} else if err := processUpdate(ctx, cfg.Config, ui.Writer(), cache, prompt.New()); err != nil {
return errors.Wrap(err, "failed to update Evans")
}

if err := mode.RunAsREPLMode(cfg.Config, ui, cache); err != nil {
return errors.Wrap(err, "failed to run REPL mode")
}

// Always call cancel func because it is hope to abort update checking if REPL mode is finished
// before update checking. If update checking is finished before REPL mode, cancel do nothing.
cancel()
if err := eg.Wait(); err != nil {
return errors.Wrap(err, "failed to check application update")
}
} else if err := mode.RunAsCLIMode(cfg.Config, cfg.call, cfg.file, ui); err != nil {
return runREPLCommand(cfg, ui)
}
invoker, err := mode.NewCallCLIInvoker(ui, cfg.call, cfg.file, cfg.Config.Request.Header)
if err != nil {
return err
}
if err := mode.RunAsCLIMode(cfg.Config, invoker); err != nil {
return errors.Wrap(err, "failed to run CLI mode")
}

Expand Down Expand Up @@ -217,20 +196,24 @@ func newCLICommand(flags *flags, ui cui.UI) *cobra.Command {
}
call = args[0]
}
if err := mode.RunAsCLIMode(cfg.Config, call, cfg.file, ui); err != nil {
invoker, err := mode.NewCallCLIInvoker(ui, call, cfg.file, cfg.Config.Request.Header)
if err != nil {
return err
}
if err := mode.RunAsCLIMode(cfg.Config, invoker); err != nil {
return errors.Wrap(err, "failed to run CLI mode")
}
return nil
}),
SilenceErrors: true,
SilenceUsage: true,
}
f := cmd.LocalFlags()
f := cmd.Flags()
initFlagSet(f, ui.Writer())
f.BoolVarP(&flags.meta.help, "help", "h", false, "display help text and exit")
cmd.SetHelpFunc(usageFunc(ui.Writer()))
cmd.AddCommand(
newCLICallCommand(flags, ui),
newCLIListCommand(flags, ui),
)
return cmd
}
Expand All @@ -240,48 +223,52 @@ func newREPLCommand(flags *flags, ui cui.UI) *cobra.Command {
Use: "repl [options ...]",
Short: "REPL mode",
RunE: runFunc(flags, func(_ *cobra.Command, cfg *mergedConfig) error {
cache, err := cache.Get()
if err != nil {
return errors.Wrap(err, "failed to get the cache content")
}

baseCtx, cancel := context.WithCancel(context.Background())
defer cancel()
eg, ctx := errgroup.WithContext(baseCtx)
// Run update checker asynchronously.
eg.Go(func() error {
return checkUpdate(ctx, cfg.Config, cache)
})

if cfg.Config.Meta.AutoUpdate {
eg.Go(func() error {
return processUpdate(ctx, cfg.Config, ui.Writer(), cache, prompt.New())
})
} else if err := processUpdate(ctx, cfg.Config, ui.Writer(), cache, prompt.New()); err != nil {
return errors.Wrap(err, "failed to update Evans")
}

if err := mode.RunAsREPLMode(cfg.Config, ui, cache); err != nil {
return errors.Wrap(err, "failed to run REPL mode")
}

// Always call cancel func because it is hope to abort update checking if REPL mode is finished
// before update checking. If update checking is finished before REPL mode, cancel do nothing.
cancel()
if err := eg.Wait(); err != nil {
return errors.Wrap(err, "failed to check application update")
}
return nil
return runREPLCommand(cfg, ui)
}),
SilenceErrors: true,
SilenceUsage: true,
}
bindREPLFlags(cmd.LocalFlags(), flags, ui.Writer())
bindREPLFlags(cmd.Flags(), flags, ui.Writer())
cmd.SetHelpFunc(usageFunc(ui.Writer()))
return cmd
}

func bindCLIFlags(f *pflag.FlagSet, flags *flags, w io.Writer) {
func runREPLCommand(cfg *mergedConfig, ui cui.UI) error {
cache, err := cache.Get()
if err != nil {
return errors.Wrap(err, "failed to get the cache content")
}

baseCtx, cancel := context.WithCancel(context.Background())
defer cancel()
eg, ctx := errgroup.WithContext(baseCtx)
// Run update checker asynchronously.
eg.Go(func() error {
return checkUpdate(ctx, cfg.Config, cache)
})

if cfg.Config.Meta.AutoUpdate {
eg.Go(func() error {
return processUpdate(ctx, cfg.Config, ui.Writer(), cache, prompt.New())
})
} else if err := processUpdate(ctx, cfg.Config, ui.Writer(), cache, prompt.New()); err != nil {
return errors.Wrap(err, "failed to update Evans")
}

if err := mode.RunAsREPLMode(cfg.Config, ui, cache); err != nil {
return errors.Wrap(err, "failed to run REPL mode")
}

// Always call cancel func because it is hope to abort update checking if REPL mode is finished
// before update checking. If update checking is finished before REPL mode, cancel do nothing.
cancel()
if err := eg.Wait(); err != nil {
return errors.Wrap(err, "failed to check application update")
}
return nil
}

func bindCLICallFlags(f *pflag.FlagSet, flags *flags, w io.Writer) {
initFlagSet(f, w)
f.StringVarP(&flags.cli.file, "file", "f", "", "a script file that will be executed by (used only CLI mode)")
f.BoolVarP(&flags.meta.help, "help", "h", false, "display help text and exit")
Expand All @@ -303,10 +290,14 @@ func printOptions(w io.Writer, f *pflag.FlagSet) {
logger.Printf("failed to write string: %s", err)
}
tw := tabwriter.NewWriter(w, 0, 8, 8, ' ', tabwriter.TabIndent)
var hasHelp bool
f.VisitAll(func(f *pflag.Flag) {
if f.Hidden {
return
}
if f.Name == "help" {
hasHelp = true
}
cmd := "--" + f.Name
if f.Shorthand != "" {
cmd += ", -" + f.Shorthand
Expand All @@ -321,6 +312,12 @@ func printOptions(w io.Writer, f *pflag.FlagSet) {
}
fmt.Fprintf(tw, " %s\t%s\n", cmd, usage)
})
// Always show --help text.
if !hasHelp {
cmd := "--help, -h"
usage := `display help text and exit (default "false")`
fmt.Fprintf(tw, " %s\t%s\n", cmd, usage)
}
tw.Flush()
}

Expand All @@ -339,9 +336,21 @@ func usageFunc(out io.Writer) func(*cobra.Command, []string) {
}

printVersion(out)
fmt.Fprint(out, "\n")
var buf bytes.Buffer
printOptions(&buf, cmd.LocalFlags())
fmt.Fprintf(out, usageFormat, strings.Join(shortUsages, " "), buf.String())
fmt.Fprintf(out, "Usage: %s\n\n", strings.Join(shortUsages, " "))
if cmd.Long != "" {
fmt.Fprint(out, cmd.Long)
fmt.Fprint(out, "\n\n")
}
if cmd.Example != "" {
fmt.Fprint(out, "Examples:\n")
fmt.Fprint(out, cmd.Example)
fmt.Fprint(out, "\n\n")
}
fmt.Fprint(out, buf.String())
fmt.Fprint(out, "\n")

if len(cmd.Commands()) > 0 {
fmt.Fprintf(out, "Available Commands:\n")
Expand All @@ -351,7 +360,8 @@ func usageFunc(out io.Writer) func(*cobra.Command, []string) {
if c.Name() == "help" {
continue
}
fmt.Fprintf(w, " %s\t%s\n", c.Name(), c.Short)
cmdAndAliases := append([]string{c.Name()}, c.Aliases...)
fmt.Fprintf(w, " %s\t%s\n", strings.Join(cmdAndAliases, ", "), c.Short)
}
w.Flush()
fmt.Fprintf(out, "\n")
Expand Down
8 changes: 0 additions & 8 deletions app/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ import (
"github.com/pkg/errors"
)

var (
usageFormat = `
Usage: %s
%s
`
)

// flags defines available command line flags.
type flags struct {
mode struct {
Expand Down
2 changes: 1 addition & 1 deletion cache/cache.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ var decodeTOML = func(r io.Reader, i interface{}) error {
}

// Get returns loaded cache contents.
func Get() (*Cache, error) {
var Get = func() (*Cache, error) { // Use variable for mocking from means_dev.go.
p := resolvePath()

if _, err := os.Stat(p); os.IsNotExist(err) {
Expand Down
Loading

0 comments on commit 27410ec

Please sign in to comment.