cli/command.go (258 lines of code) (raw):
// Copyright (c) 2009-present, Alibaba Cloud All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cli
import (
"fmt"
"strings"
"github.com/aliyun/aliyun-cli/v3/i18n"
)
type Command struct {
// Command Name
Name string
// Short is the short description shown in the 'help' output.
Short *i18n.Text
// Long is the long message shown in the 'help <this-command>' output.
Long *i18n.Text
// Syntax for usage
Usage string
// Sample command
Sample string
// Enable unknown flags
EnableUnknownFlag bool
// enable suggest distance,
// disable -1
// 0: default distance
SuggestDistance int
// Hidden command
Hidden bool
// Run, command error will be catch
Run func(ctx *Context, args []string) error
// Help
Help func(ctx *Context, args []string) error
// auto compete
AutoComplete func(ctx *Context, args []string) []string
parent *Command
subCommands []*Command
flags *FlagSet
// Keep args
KeepArgs bool
}
func (c *Command) AddSubCommand(cmd *Command) {
cmd.parent = c
c.subCommands = append(c.subCommands, cmd)
}
func (c *Command) Flags() *FlagSet {
if c.flags == nil {
c.flags = NewFlagSet()
}
return c.flags
}
func (c *Command) Execute(ctx *Context, args []string) {
if ctx.completion != nil {
args = ctx.completion.GetArgs()
}
err := c.executeInner(ctx, args)
if err != nil {
c.processError(ctx, err)
}
}
func (c *Command) getName() string {
if c.parent == nil {
return c.Name
}
return c.parent.getName() + " " + c.Name
}
type Metadata struct {
Name string `json:"name"`
Short map[string]string `json:"short"`
Long map[string]string `json:"long"`
Usage string `json:"usage"`
Sample string `json:"sample"`
Hidden bool `json:"hidden"`
Flags map[string]*MetadataFlag `json:"flags"`
}
type MetadataFlag struct {
Name string `json:"name"`
Shorthand rune `json:"shorthand"`
Short map[string]string `json:"short"`
Long map[string]string `json:"long"`
DefaultValue string `json:"default"`
Required bool `json:"required"`
Aliases []string `json:"aliases"`
AssignedMode int `json:"assign_mode"`
Persistent bool `json:"persistent"`
Hidden bool `json:"hidden"`
Category string `json:"category"`
}
func (c *Command) GetMetadata(metadata map[string]*Metadata) {
name := c.getName()
meta := &Metadata{}
meta.Name = name
meta.Short = c.Short.GetData()
if c.Long != nil {
meta.Long = c.Long.GetData()
}
meta.Usage = c.Usage
meta.Sample = c.Sample
meta.Hidden = c.Hidden
meta.Flags = make(map[string]*MetadataFlag)
for _, flag := range c.Flags().Flags() {
f := &MetadataFlag{}
f.Name = flag.Name
f.Shorthand = flag.Shorthand
if flag.Short != nil {
f.Short = flag.Short.GetData()
}
if flag.Long != nil {
f.Long = flag.Long.GetData()
}
f.DefaultValue = flag.DefaultValue
f.Required = flag.Required
f.Aliases = flag.Aliases
f.AssignedMode = int(flag.AssignedMode)
f.Persistent = flag.Persistent
f.Hidden = flag.Hidden
f.Category = flag.Category
// Flag can assigned with --flag field1=value1 field2=value2 value3 ...
// must used with AssignedMode=AssignedRepeatable
// Fields []Field
// Flag can't appear with other flags, use Flag.Name
// ExcludeWith []string
meta.Flags[flag.Name] = f
}
metadata[name] = meta
for _, cmd := range c.subCommands {
cmd.GetMetadata(metadata)
}
}
func (c *Command) GetSubCommand(s string) *Command {
for _, cmd := range c.subCommands {
if cmd.Name == s {
return cmd
}
}
return nil
}
func (c *Command) GetSuggestions(s string) []string {
sr := NewSuggester(s, c.GetSuggestDistance())
for _, cmd := range c.subCommands {
sr.Apply(cmd.Name)
}
return sr.GetResults()
}
func (c *Command) GetSuggestDistance() int {
if c.SuggestDistance < 0 {
return 0
} else if c.SuggestDistance == 0 {
return DefaultSuggestDistance
} else {
return c.SuggestDistance
}
}
func (c *Command) GetUsageWithParent() string {
usage := c.Usage
for p := c.parent; p != nil; p = p.parent {
usage = p.Name + " " + usage
}
return usage
}
func (c *Command) ExecuteComplete(ctx *Context, args []string) {
if strings.HasPrefix(ctx.completion.Current, "-") {
for _, f := range ctx.flags.Flags() {
if f.Hidden {
continue
}
if !strings.HasPrefix("--"+f.Name, ctx.completion.Current) {
continue
}
Printf(ctx.Stdout(), "--%s\n", f.Name)
}
} else {
for _, sc := range c.subCommands {
if sc.Hidden {
continue
}
if !strings.HasPrefix(sc.Name, ctx.completion.Current) {
continue
}
Printf(ctx.Stdout(), "%s\n", sc.Name)
}
}
}
func (c *Command) executeInner(ctx *Context, args []string) error {
// fmt.Printf(">>> Execute Command: %s args=%v\n", c.Name, args)
parser := NewParser(args, ctx)
var current = parser.GetCurrent()
// get next arg
nextArg, _, err := parser.ReadNextArg()
if err != nil {
return err
}
// if next arg is help, run help
if nextArg == "help" {
ctx.help = true
return c.executeInner(ctx, parser.GetRemains())
}
// if next args is not empty, try find sub commands
if nextArg != "" {
// if has sub command, run it
subCommand := c.GetSubCommand(nextArg)
if subCommand != nil {
ctx.EnterCommand(subCommand)
return subCommand.executeInner(ctx, parser.GetRemains())
}
// no sub command and command.Run == nil
// raise error
if c.Run == nil {
// c.executeHelp(ctx, args, fmt.Errorf("unknown command: %s", nextArg))
return NewInvalidCommandError(nextArg, ctx)
}
}
var remainArgs []string
if !c.KeepArgs {
// cmd is find by args, try run cmd.Run
// parse remain args
remainArgs, err = parser.ReadAll()
if err != nil {
return fmt.Errorf("parse failed %s", err)
}
} else {
remainArgs = args[current:]
}
// check flags
err = ctx.CheckFlags()
if err != nil {
return err
}
if HelpFlag(ctx.Flags()).IsAssigned() {
ctx.help = true
}
callArgs := make([]string, 0)
if nextArg != "" {
if !c.KeepArgs {
callArgs = append(callArgs, nextArg)
}
}
for _, s := range remainArgs {
if s != "help" {
callArgs = append(callArgs, s)
} else {
ctx.help = true
}
}
if ctx.completion != nil {
if c.AutoComplete != nil {
ss := c.AutoComplete(ctx, callArgs)
for _, s := range ss {
Printf(ctx.Stdout(), "%s\n", s)
}
} else {
c.ExecuteComplete(ctx, callArgs)
}
return nil
}
if ctx.help {
c.executeHelp(ctx, callArgs)
return nil
} else if c.Run == nil {
c.executeHelp(ctx, callArgs)
return nil
}
return c.Run(ctx, callArgs)
}
func (c *Command) processError(ctx *Context, err error) {
Errorf(ctx.Stderr(), "ERROR: %s\n", err.Error())
if e, ok := err.(SuggestibleError); ok {
PrintSuggestions(ctx, i18n.GetLanguage(), e.GetSuggestions())
Exit(2)
return
}
if e, ok := err.(ErrorWithTip); ok {
Noticef(ctx.Stderr(), "\n%s\n", e.GetTip(i18n.GetLanguage()))
Exit(3)
return
}
Exit(1)
}
func (c *Command) executeHelp(ctx *Context, args []string) {
if c.Help != nil {
err := c.Help(ctx, args)
if err != nil {
c.processError(ctx, err)
}
return
}
c.PrintHead(ctx)
c.PrintUsage(ctx)
c.PrintSubCommands(ctx)
c.PrintFlags(ctx)
c.PrintTail(ctx)
}