forked from alecthomas/kingpin
-
Notifications
You must be signed in to change notification settings - Fork 0
/
app.go
159 lines (144 loc) · 3.97 KB
/
app.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
// Package commander is used to manage a set of command-line "commands", with
// per-command flags and arguments.
//
// Supports command like so:
//
// <command> <required> [<optional> [<optional> ...]]
// <command> <remainder...>
//
// eg.
//
// register [--name <name>] <nick>|<id>
// post --channel|-c <channel> [--image <image>] [<text>]
//
// var (
// chat = commander.New()
// debug = chat.Flag("debug", "enable debug mode").Default("false").Bool()
//
// register = chat.Command("register", "Register a new user.")
// registerName = register.Flag("name", "name of user").Required().String()
// registerNick = register.Arg("nick", "nickname for user").Required().String()
//
// post = chat.Command("post", "Post a message to a channel.")
// postChannel = post.Flag("channel", "channel to post to").Short('c').Required().String()
// postImage = post.Flag("image", "image to post").String()
// )
//
package kingpin
import (
"fmt"
"os"
)
type Dispatch func() error
// An Application contains the definitions of flags, arguments and commands
// for an application.
type Application struct {
*flagGroup
*argGroup
Name string
Help string
commands map[string]*CmdClause
commandOrder []*CmdClause
commandHelp *string
}
// New creates a new Kingpin application instance.
func New(name, help string) *Application {
c := &Application{
flagGroup: newFlagGroup(),
argGroup: newArgGroup(),
Name: name,
Help: help,
commands: make(map[string]*CmdClause),
}
c.Flag("help", "Show help.").Dispatch(c.onFlagHelp).Bool()
return c
}
func (a *Application) onFlagHelp() error {
a.Usage(os.Stderr)
os.Exit(0)
return nil
}
// Command adds a new top-level command to the application.
func (a *Application) Command(name, help string) *CmdClause {
cmd := newCommand(a, name, help)
a.commands[name] = cmd
a.commandOrder = append(a.commandOrder, cmd)
return cmd
}
// Parse parses command-line arguments.
func (a *Application) Parse(args []string) (command string, err error) {
if err := a.init(); err != nil {
return "", err
}
tokens := Tokenize(args)
return a.parse(tokens)
}
// Version adds a --version flag for displaying the application version.
func (a *Application) Version(version string) *Application {
a.Flag("version", "Show application version.").Dispatch(func() error {
fmt.Println(version)
os.Exit(0)
return nil
}).Bool()
return a
}
func (c *Application) init() error {
if len(c.commands) > 0 && len(c.args) > 0 {
return fmt.Errorf("can't mix top-level Arg()s with Command()s")
}
if len(c.commands) > 0 {
cmd := c.Command("help", "Show help for a command.")
c.commandHelp = cmd.Arg("command", "Command name.").Required().Dispatch(c.onCommandHelp).String()
// Make "help" command first in order. Also, Go's slice operations are woeful.
c.commandOrder = append(c.commandOrder[len(c.commandOrder)-1:], c.commandOrder[:len(c.commandOrder)-1]...)
}
if err := c.flagGroup.init(); err != nil {
return err
}
if err := c.argGroup.init(); err != nil {
return err
}
for _, cmd := range c.commands {
if err := cmd.init(); err != nil {
return err
}
}
return nil
}
func (c *Application) onCommandHelp() error {
c.CommandUsage(os.Stderr, *c.commandHelp)
os.Exit(0)
return nil
}
func (c *Application) parse(tokens tokens) (command string, err error) {
// Special-case "help" to avoid issues with required flags.
runHelp := (tokens.Peek().Value == "help")
tokens, err = c.flagGroup.parse(tokens, runHelp)
if err != nil {
return "", err
}
if len(c.args) > 0 {
tokens, err = c.argGroup.parse(tokens)
if err != nil {
return "", err
}
} else {
token, tokens := tokens.Next()
switch token.Type {
case TokenArg:
cmd, ok := c.commands[token.Value]
if !ok {
return "", fmt.Errorf("unknown command '%s'", token)
}
tokens, err = cmd.parse(tokens)
if err != nil {
return "", err
}
return cmd.name, nil
case TokenEOF:
default:
return "", fmt.Errorf("unexpected '%s'", token)
}
}
return "", nil
}