• Home
  • History
  • Annotate
Name Date Size #Lines LOC

..03-May-2022-

.github/ISSUE_TEMPLATE/H06-Sep-2019-

.travis.ymlH A D06-Sep-2019285

CODE_OF_CONDUCT.mdH A D06-Sep-20193.1 KiB

LICENSEH A D06-Sep-201934.3 KiB

README.mdH A D06-Sep-20196.4 KiB

argument.goH A D06-Sep-20191.6 KiB

argument_test.goH A D06-Sep-20191.7 KiB

command_descriptor.goH A D06-Sep-2019526

command_handler.goH A D06-Sep-2019356

command_helper.goH A D06-Sep-20193 KiB

command_helper_test.goH A D06-Sep-20197.5 KiB

command_registry.goH A D06-Sep-20193.2 KiB

command_registry_test.goH A D06-Sep-201918 KiB

command_wrapper.goH A D06-Sep-2019997

example_argument_test.goH A D06-Sep-2019852

example_command_helper_test.goH A D06-Sep-20191.3 KiB

go.modH A D06-Sep-2019165

go.sumH A D06-Sep-2019408

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