File: README.md

package info (click to toggle)
golang-github-ghjm-cmdline 0.1.2-3
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 148 kB
  • sloc: makefile: 17
file content (152 lines) | stat: -rw-r--r-- 6,048 bytes parent folder | download
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
Cmdline
=======

Cmdline is a Go CLI parsing and execution framework based on the idea
that you create structs containing the fields you want the user to
provide, and then the framework handles all the details of accepting
those from the command-line or from YAML configuration files. Methods
of the structs are called in user-defined phases to execute their
functions.

## Motivation

Go already has good command line packages, so why write another? The
main reason I wrote this was that I could not find a package where it
was easy to provide multiple "things" that should all be executed at
once.  My use case was less like `git clone|add|commit|etc...` and
more like `receptor --listen 1.2.3.4:80 --listen 4.5.6.7:8080`.  I
don't want a single command with subcommands, I want many different
configuration sections, each modifying the execution of a main program.

Subsequent work on this has made it more generally useful: you
_could_ implement a CLI with subcommands like git.  And the paradigm
of writing structs, receiver functions and execution phases has
turned out to be fairly useful.  So I don't claim this is _better_
than existing libraries, just that it is _different_, and perhaps
it will be handy for your particular use case.

## Example

A simple example is provided in [cmd/example.go](cmd/example.go). This
program pretends to draw shapes.  (It doesn't actually draw anything.)
On the command line, the user can specify one or more of different shape
types, each with their own properties.  For example, the rectangle is
defined with the following struct:

```
type rectangle struct {
	Width  float64 `description:"Width of the rectangle." required:"yes"`
	Height float64 `description:"Height of the rectangle." required:"yes"`
	Color  string  `description:"Color to use when drawing the rectangle" default:"white"`
}
```

This tells the library that a rectangle must be given a width and height,
and can optionally also be given a color.  The rectangle is registered
to the cmdline system with the following call:

```
cl.AddConfigType("rectangle", "Rectangle Shape", rectangle{})
```

This tells the library that this item should be instantiated when the
user gives a `--rectangle` command, that the help description should
describe this as a `Rectangle Shape`, and that the `rectangle` struct
contains the configuration details.  Once registered, users can give
CLI parameters like `--rectangle width=3 height=4`.

Once all our CLI parameter objects are registered, the main function
of the library is invoked:

```
err := cl.ParseAndRun(os.Args[1:], []string{"Check", "Draw"}, cmdline.ShowHelpIfNoArgs)
```

This parses the command-line arguments, and then runs two execution
phases.  ShowHelpIfNoArgs means that if the user provides no command
line arguments, we should show the help and exit.

An execution phase simply calls receiver methods on the
configuration objects that have the same name as the phase.  These
methods must take no parameters and return an error value.  For
example, here is the rectangle's Check method:

```
func (r rectangle) Check() error {
	if r.Height < 0 || r.Width < 0 {
		return fmt.Errorf("rectangle height and width cannot be negative")
	}
	return nil
}
```

Phases are run in the order given, so the Check method will be run on
all instantiated objects (that have one), and if they all succeed, then
the Draw method will be run.  Any error stops the whole process.

## YAML config files

Users can supply a YAML config file instead of specifying all the arguments
on the command line, by using `--config <filename>` or `-c <filename>`.
The YAML is expected to be formatted as a map of options, with the keys
corresponding to config types and the values corresponding to the field
values of the config types.  For example, a YAML config file for rectangle
would look like this:

```
---
- rectangle:
    height: 3.0
    width: 4.0
```

This will be processed the same as if the user entered
`--rectangle height=3.0 width=4.0` on the command line.

## Options for config types

When registering a config type with `AddConfigType`, the following options
can be specified:

* `Required`: At least one of this item must appear on the command line.
* `Singleton`: Only one of this item can be instantiated.
* `Exclusive`: If this item is on the command line, then it must be the only one.
* `Hidden`: Do not include this item in help or command line completion. 
* `Section`: Group this item within a given section in the help output.

## Options for configuration items

Fields within a config type struct use struct tags to control their function.
The following tags can be applied to a struct field:

* `description`: Description to show to the user in help output.
* `required`: If true, the field must be provided.
* `default`: Default value to use if the user does not specify one.
* `barevalue`: If true, this field can be used as a bare value.    
  (A bare value is like `--item 37` rather than `--item id=37`.)
* `ignore`: If true, do not use this field for command line processing at all.    
  (This can be used for private data to be passed between phases, etc.)

## Data types

The cmdline library attempts to handle a large variety of data types by
using the type conversion capabilities of the reflect package.  In most
cases you can just declare the struct field as the type you want, and
the string from the CLI will be converted to it.

If the destination field is a slice or map, then the user can either
supply a JSON string on the command line itself, or use a YAML configuration
file to define the list or dict.  Complex data types are possible, but
become cumbersome when not using YAML configuration files.

In the special case where a struct field is a `[]string`, the library
will combine multiple single-string values.  For example, if you have:

```
type listen struct {
	IP []string `description:"IP address to listen on"`
}
```

then the user can do `--listen ip=1.2.3.4 ip=2.3.4.5`, and the struct
field will be filled in with a list of the provided values.