README.md
1[![Documentation](https://godoc.org/github.com/yitsushi/go-commander?status.svg)](http://godoc.org/github.com/yitsushi/go-commander)
2[![Go Report Card](https://goreportcard.com/badge/github.com/yitsushi/go-commander)](https://goreportcard.com/report/github.com/yitsushi/go-commander)
3[![Coverage Status](https://coveralls.io/repos/github/yitsushi/go-commander/badge.svg)](https://coveralls.io/github/yitsushi/go-commander)
4[![Build Status](https://travis-ci.org/yitsushi/go-commander.svg?branch=master)](https://travis-ci.org/yitsushi/go-commander)
5
6This is a simple Go library to manage commands for your CLI tool.
7Easy to use and now you can focus on Business Logic instead of building
8the command routing.
9
10### What this library does for you?
11
12Manage your separated commands. How? Generates a general help and command
13specific helps for your commands. If your command fails somewhere
14(`panic` for example), commander will display the error message and
15the command specific help to guide your user.
16
17### Install
18
19```shell
20$ go get github.com/yitsushi/go-commander
21```
22
23### Sample output _(from [totp-cli](https://github.com/yitsushi/totp-cli))_
24
25```shell
26$ totp-cli help
27
28change-password Change password
29update Check and update totp-cli itself
30version Print current version of this application
31generate <namespace>.<account> Generate a specific OTP
32add-token [namespace] [account] Add new token
33list [namespace] List all available namespaces or accounts under a namespace
34delete <namespace>[.account] Delete an account or a whole namespace
35help [command] Display this help or a command specific help
36```
37
38### Usage
39
40Every single command has to implement `CommandHandler`.
41Check [this project](https://github.com/yitsushi/totp-cli) for examples.
42
43```go
44package main
45
46// Import the package
47import "github.com/yitsushi/go-commander"
48
49// Your Command
50type YourCommand struct {
51}
52
53// Executed only on command call
54func (c *YourCommand) Execute(opts *commander.CommandHelper) {
55 // Command Action
56}
57
58func NewYourCommand(appName string) *commander.CommandWrapper {
59 return &commander.CommandWrapper{
60 Handler: &YourCommand{},
61 Help: &commander.CommandDescriptor{
62 Name: "your-command",
63 ShortDescription: "This is my own command",
64 LongDescription: `This is a very long
65description about this command.`,
66 Arguments: "<filename> [optional-argument]",
67 Examples: []string {
68 "test.txt",
69 "test.txt copy",
70 "test.txt move",
71 },
72 },
73 }
74}
75
76// Main Section
77func main() {
78 registry := commander.NewCommandRegistry()
79
80 registry.Register(NewYourCommand)
81
82 registry.Execute()
83}
84```
85
86Now you have a CLI tool with two commands: `help` and `your-command`.
87
88```bash
89❯ go build mytool.go
90
91❯ ./mytool
92your-command <filename> [optional-argument] This is my own command
93help [command] Display this help or a command specific help
94
95❯ ./mytool help your-command
96Usage: mytool your-command <filename> [optional-argument]
97
98This is a very long
99description about this command.
100
101Examples:
102 mytool your-command test.txt
103 mytool your-command test.txt copy
104 mytool your-command test.txt move
105```
106
107#### How to use subcommand pattern?
108
109When you create your main command, just create a new `CommandRegistry` inside
110the `Execute` function like you did in your `main()` and change `Depth`.
111
112```go
113import subcommand "github.com/yitsushi/mypackage/command/something"
114
115func (c *Something) Execute(opts *commander.CommandHelper) {
116 registry := commander.NewCommandRegistry()
117 registry.Depth = 1
118 registry.Register(subcommand.NewSomethingMySubCommand)
119 registry.Execute()
120}
121```
122
123### PreValidation
124
125If you want to write a general pre-validation for your command
126or just simply keep your validation logic separated:
127
128```go
129// Or you can define inline if you want
130func MyValidator(c *commander.CommandHelper) {
131 if c.Arg(0) == "" {
132 panic("File?")
133 }
134
135 info, err := os.Stat(c.Arg(0))
136 if err != nil {
137 panic("File not found")
138 }
139
140 if !info.Mode().IsRegular() {
141 panic("It's not a regular file")
142 }
143
144 if c.Arg(1) != "" {
145 if c.Arg(1) != "copy" && c.Arg(1) != "move" {
146 panic("Invalid operation")
147 }
148 }
149}
150
151func NewYourCommand(appName string) *commander.CommandWrapper {
152 return &commander.CommandWrapper{
153 Handler: &YourCommand{},
154 Validator: MyValidator
155 Help: &commander.CommandDescriptor{
156 Name: "your-command",
157 ShortDescription: "This is my own command",
158 LongDescription: `This is a very long
159description about this command.`,
160 Arguments: "<filename> [optional-argument]",
161 Examples: []string {
162 "test.txt",
163 "test.txt copy",
164 "test.txt move",
165 },
166 },
167 }
168}
169```
170
171
172### Define arguments with type
173
174```go
175&commander.CommandWrapper{
176 Handler: &MyCommand{},
177 Arguments: []*commander.Argument{
178 &commander.Argument{
179 Name: "list",
180 Type: "StringArray[]",
181 },
182 },
183 Help: &commander.CommandDescriptor{
184 Name: "my-command",
185 },
186}
187```
188
189In your command:
190
191```go
192if opts.HasValidTypedOpt("list") == nil {
193 myList := opts.TypedOpt("list").([]string)
194 if len(myList) > 0 {
195 mockPrintf("My list: %v\n", myList)
196 }
197}
198```
199
200#### Define own type
201
202Yes you can ;)
203
204```go
205// Define your struct (optional)
206type MyCustomType struct {
207 ID uint64
208 Name string
209}
210
211// register your own type with parsing/validation
212commander.RegisterArgumentType("MyType", func(value string) (interface{}, error) {
213 values := strings.Split(value, ":")
214
215 if len(values) < 2 {
216 return &MyCustomType{}, errors.New("Invalid format! MyType => 'ID:Name'")
217 }
218
219 id, err := strconv.ParseUint(values[0], 10, 64)
220 if err != nil {
221 return &MyCustomType{}, errors.New("Invalid format! MyType => 'ID:Name'")
222 }
223
224 return &MyCustomType{
225 ID: id,
226 Name: values[1],
227 },
228 nil
229})
230
231// Define your command
232&commander.CommandWrapper{
233 Handler: &MyCommand{},
234 Arguments: []*commander.Argument{
235 &commander.Argument{
236 Name: "owner",
237 Type: "MyType",
238 FailOnError: true, // Optional boolean
239 },
240 },
241 Help: &commander.CommandDescriptor{
242 Name: "my-command",
243 },
244}
245```
246
247In your command:
248
249```go
250if opts.HasValidTypedOpt("owner") == nil {
251 owner := opts.TypedOpt("owner").(*MyCustomType)
252 mockPrintf("OwnerID: %d, Name: %s\n", owner.ID, owner.Name)
253}
254```
255