进程管理程序
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
pmt/cmd/command.go

379 lines
7.8 KiB

package cmd
import (
"errors"
"fmt"
"os"
"strings"
)
type DyadicValue struct {
Value any
Another any
}
func (d *DyadicValue) GetValue() any {
if d.Value != nil {
return d.Value
}
return d.Another
}
type CommandAction func(*Command, map[string]any, ...any) error
type Command struct {
name string
usage *DyadicValue
version *Option
help *Option
description string
longDescription string
arguments []*Argument
options []*Option
types map[string]TypeParser
globalTypes map[string]TypeParser
commands []*Command
program *Command
action CommandAction
}
func New(name string, desc ...string) *Command {
cmd := &Command{
types: make(map[string]TypeParser),
}
cmd.Version("1.0.0").Help("")
switch len(desc) {
case 0:
return cmd.Name(name)
case 1:
return cmd.Name(name).Description(desc[0])
default:
return cmd.Name(name).Description(desc[0], desc[1:]...)
}
}
func (c *Command) Name(name string) *Command {
if len(name) == 0 {
panic(errors.New("empty command name"))
}
// 仅仅支持二级,所以只需要检查 Command.program.commands 中是否重复
if c.program != nil {
for _, command := range c.program.commands {
if command.name == name {
panic(errors.New("duplicated command name"))
}
}
}
c.name = name
return c
}
func (c *Command) GetName() string {
return c.name
}
func (c *Command) Type(name string, parser func(...string) any) *Command {
c.types[name] = parser
return c
}
func (c *Command) GlobalType(name string, parser func(...string) any) *Command {
if c.program != nil {
c.program.GlobalType(name, parser)
} else {
c.globalTypes[name] = parser
}
return c
}
func (c *Command) Command(name string, desc ...string) *Command {
program := c.program
if program == nil {
program = c
}
cmd := New(name, desc...)
cmd.program = program
program.commands = append(program.commands, cmd)
return cmd
}
func (c *Command) Version(ver string) *Command {
if c.version == nil {
c.VersionOption("-v, --version", "Show version number")
}
if len(ver) > 0 {
c.version.RuntimeValue = []string{ver}
} else {
c.version.RuntimeValue = make([]string, 0)
}
return c
}
func (c *Command) VersionOption(expr string, desc ...string) *Command {
ver, err := ensureOption(c, expr, desc, "version")
if errors.Is(err, ErrOptionDefinition) {
panic(ErrVersionDefinition)
} else if err != nil {
panic(err)
}
if c.help != nil {
err = hasConflicts(c.help, ver)
if err != nil {
panic(errors.New("version option conflicts with help option, " + err.Error()))
}
}
c.version = ver
return c
}
func (c *Command) GetVersion(opts ...*ShowOptions) string {
if c.program != nil {
return c.program.GetVersion()
}
if c.version == nil {
c.Version("1.0.0")
}
return versionToString(c, resolveShowOptions(opts))
}
func (c *Command) ShowVersion(opts ...*ShowOptions) error {
return showMessage(resolveShowOptions(opts), c.GetVersion)
}
func (c *Command) Usage(usage string) *Command {
if c.usage == nil {
c.usage = new(DyadicValue)
}
c.usage.Value = usage
return c
}
func (c *Command) GetUsage(opts ...*ShowOptions) string {
if c.usage != nil {
usage := c.usage.GetValue()
if usage != nil {
return usage.(string)
}
}
s := usageToString(c, resolveShowOptions(opts))
c.usage = new(DyadicValue)
c.usage.Another = s
return s
}
func (c *Command) ShowUsage(opts ...*ShowOptions) error {
return showMessage(resolveShowOptions(opts), c.GetUsage)
}
func (c *Command) Description(desc string, longDesc ...string) *Command {
c.description = desc
if len(longDesc) > 0 {
c.longDescription = strings.Join(longDesc, "\n")
}
return c
}
func (c *Command) GetDescription(opts ...*ShowOptions) string {
o := resolveShowOptions(opts)
var s string
if o.Long {
s = c.longDescription
} else {
s = c.description
}
if len(s) == 0 {
return ""
}
if o.Titles {
return "DESCRIPTION:\n " + s
}
return s
}
func (c *Command) ShowDescription(opts ...*ShowOptions) error {
return showMessage(resolveShowOptions(opts), c.GetDescription)
}
func (c *Command) Help(help string) *Command {
if c.help == nil {
c.HelpOption("-h, --help", "Show help information")
}
if len(help) > 0 {
c.help.RuntimeValue = []string{help}
} else {
c.help.RuntimeValue = make([]string, 0)
}
return c
}
func (c *Command) HelpOption(expr string, desc ...string) *Command {
help, err := ensureOption(c, expr, desc, "help")
if errors.Is(err, ErrOptionDefinition) {
panic(ErrVersionDefinition)
} else if err != nil {
panic(err)
}
if c.version != nil {
err = hasConflicts(help, c.version)
if err != nil {
panic(errors.New("help option conflicts with version option, " + err.Error()))
}
}
c.help = help
return c
}
func (c *Command) GetHelp(opts ...*ShowOptions) string {
if c.help != nil {
if len(c.help.RuntimeValue) > 0 {
return c.help.RuntimeValue[0]
}
if c.help.DefaultValue != nil {
return c.help.DefaultValue.(string)
}
}
s := helpToString(c, resolveShowOptions(opts))
c.help.DefaultValue = s
return s
}
func (c *Command) ShowHelp(opts ...*ShowOptions) error {
return showMessage(resolveShowOptions(opts), c.GetHelp)
}
func (c *Command) Argument(expr string, desc ...string) *Command {
a, err := createArgument(expr)
if err != nil {
panic(err)
}
switch len(desc) {
case 0:
break
case 1:
a.Description = desc[0]
default:
a.Description = desc[0]
a.LongDescription = desc[1]
}
var lastArg *Argument
for _, a2 := range c.arguments {
lastArg = a2
if a2.Name == a.Name {
panic(errors.New("duplicated arguments definition"))
}
}
if lastArg != nil {
if lastArg.Variadic {
panic(errors.New("after variadic argument"))
}
if a.Required && !lastArg.Required {
panic(errors.New("after optional argument"))
}
}
c.arguments = append(c.arguments, a)
return c
}
func (c *Command) Option(expr string, desc ...string) *Command {
o, err := ensureOption(c, expr, desc, "")
if err != nil {
panic(err)
}
if c.version != nil {
err = hasConflicts(c.version, o)
if err != nil {
panic(err)
}
}
if c.help != nil {
err = hasConflicts(c.help, o)
if err != nil {
panic(err)
}
}
c.options = append(c.options, o)
return c
}
func (c *Command) Action(action func(*Command, map[string]any, ...any) error) *Command {
c.action = action
return c
}
func (c *Command) Run(args ...[]string) error {
var argv []string
for _, arg := range args {
argv = arg
break
}
if argv == nil {
argv = os.Args[1:]
}
if c.program != nil {
return c.program.Run(argv)
}
command := c
cmdIdx := -1
for i, arg := range argv {
if !strings.HasPrefix(argv[0], "-") {
for _, cmd := range c.commands {
if cmd.name == arg {
command = cmd
cmdIdx = i
break
}
}
break
}
}
if cmdIdx == 0 {
argv = argv[1:]
} else if cmdIdx == len(argv)-1 {
argv = argv[:cmdIdx]
} else if cmdIdx > -1 {
argv = append(argv[:cmdIdx], argv[cmdIdx+1:]...)
}
help, version, options, arguments := parseArgs(argv, command)
opts := resolveShowOptions([]*ShowOptions{})
if help {
return command.ShowHelp(opts)
} else if version {
return command.ShowVersion(&ShowOptions{
Colors: opts.Colors,
Types: opts.Types,
Long: opts.Long,
Titles: false,
LineWidth: opts.LineWidth,
Writer: opts.Writer,
})
} else if command.action == nil {
fmt.Println("没有操作,则显示帮助文档")
// 没有操作,则显示帮助文档
return command.ShowHelp()
} else {
return command.action(command, options, arguments...)
}
}
func hasConflicts(o1, o2 *Option) error {
if len(o1.Short) > 0 && o1.Short == o2.Short {
return errors.New("duplicated short flag of option definition")
}
if o1.Long == o2.Long {
return errors.New("duplicated long flag of option definition")
}
if o1.Name == o2.Name {
return errors.New("duplicated arguments definition")
}
return nil
}