Skip to content

Commit db1c639

Browse files
author
Omeid Matten
committed
better docs and usage message
1 parent fedfbe0 commit db1c639

6 files changed

Lines changed: 140 additions & 69 deletions

File tree

README.md

Lines changed: 77 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -55,11 +55,17 @@ type Config struct {
5555

5656
Redis redis.Config
5757
Database database.Config
58+
59+
// the flags plugin allows capturing a single Command after the flags.
60+
// so you can run myprogram -flag=value -s -blah=bleh stop|start|stop and so on.
61+
Mode string `default:"start" flag:",command" usage:"run|start|stop"`
5862
}
5963

6064
var files = uconfig.Files{
61-
{"config.json", json.Unmarshal, true},
62-
// you can of course add as many files
65+
{Path: "/etc/demo-app/config.json", Unmarshal: json.Unmarshal, Optional: true},
66+
{Path: "config.json", Unmarshal: json.Unmarshal, Optional: true},
67+
// or short form {"config.json", json.Unmarshal, true},
68+
// And, of course, you can of course add as many files
6369
// as you want, and they will be applied
6470
// in the given order.
6571
}
@@ -84,22 +90,31 @@ Now lets run our program:
8490

8591
```sh
8692
$ go run main.go -h
93+
Usage:
94+
main [flags] [command]
95+
96+
Configurations:
97+
FIELD FLAG ENV DEFAULT USAGE
98+
----- ----- ----- ------- -----
99+
Hosts -hosts HOSTS localhost,localhost.local the ip or domains to bind to
100+
Redis.Address -redis-address REDIS_ADDRESS redis-master
101+
Redis.Port -redis-port REDIS_PORT 6379
102+
Redis.Password -redis-password REDIS_PASSWORD
103+
Redis.DB -redis-db REDIS_DB 0
104+
Redis.Expire -redis-expire REDIS_EXPIRE 5s
105+
Database.Address -database-address DATABASE_ADDRESS localhost
106+
Database.Port -database-port SERVICE_PORT 28015
107+
Database.Database -database-database DB my-project
108+
Mode [command] MODE start run|start|stop
109+
110+
Configuration Files:
111+
/etc/demo-app/config.json
112+
config.json
87113

88-
Supported Fields:
89-
FIELD FLAG ENV DEFAULT USAGE
90-
----- ----- ----- ------- -----
91-
Hosts -hosts HOSTS localhost,localhost.local the ip or domains to bind to
92-
Redis.Address -redis-address REDIS_ADDRESS redis-master
93-
Redis.Port -redis-port REDIS_PORT 6379
94-
Redis.Password -redis-password REDIS_PASSWORD
95-
Redis.DB -redis-db REDIS_DB 0
96-
Redis.Expire -redis-expire REDIS_EXPIRE 5s
97-
Database.Address -database-address DATABASE_ADDRESS localhost
98-
Database.Port -database-port DATABASE_PORT 28015
99-
Database.Database -database-database DATABASE_DATABASE my-project
100-
114+
```
101115
$ go run main.go
102116

117+
```json
103118
{
104119
"Hosts": [
105120
"localhost",
@@ -116,7 +131,8 @@ $ go run main.go
116131
"Address": "localhost",
117132
"Port": "28015",
118133
"Database": "my-project"
119-
}
134+
},
135+
"Mode": "start"
120136
}
121137

122138
```
@@ -171,19 +187,23 @@ type Config struct {
171187

172188
Which should give you the following settings:
173189

174-
```
175-
Supported Fields:
176-
FIELD FLAG ENV DEFAULT USAGE
177-
----- ----- ----- ------- -----
178-
Hosts -hosts HOSTS localhost,localhost.local the ip or domains to bind to
179-
Redis.Port -redis-port REDIS_PORT 6379
180-
Redis.Password -redis-password REDIS_PASSWORD
181-
Redis.DB -redis-db REDIS_DB 0
182-
Redis.Expire -redis-expire REDIS_EXPIRE 5s
183-
Database.Address -database-address DATABASE_ADDRESS localhost
184-
Database.Service.Port -database-service-port DATABASE_SERVICE_PORT 28015
185-
Database.Database -main-db-db DB_NAME my-project
186-
exit status 1
190+
```sh
191+
$ go run main.go -h
192+
Usage:
193+
main [flags] [command]
194+
195+
Configurations:
196+
FIELD FLAG ENV DEFAULT USAGE
197+
----- ----- ----- ------- -----
198+
Hosts -hosts HOSTS localhost,localhost.local the ip or domains to bind to
199+
Redis.Address -redis-address REDIS_ADDRESS redis-master
200+
Redis.Port -redis-port REDIS_PORT 6379
201+
Redis.Password -redis-password REDIS_PASSWORD
202+
Redis.DB -redis-db REDIS_DB 0
203+
Redis.Expire -redis-expire REDIS_EXPIRE 5s
204+
Database.Address -database-address DATABASE_ADDRESS localhost
205+
Database.Database -main-db-db DB_NAME my-project
206+
Database.Service.Port -database-service-port DATABASE_SERVICE_PORT 28015
187207
```
188208

189209

@@ -209,52 +229,49 @@ Unlike most other plugins, secret requires explicit `secret:""` tag, this is bec
209229
package main
210230

211231
import (
212-
"encoding/json"
213-
"fmt"
232+
"encoding/json"
233+
"fmt"
214234

215-
"github.com/omeid/uconfig"
216-
"github.com/omeid/uconfig/examples/secrets/secretsource"
217-
"github.com/omeid/uconfig/plugins/secret"
235+
"github.com/omeid/uconfig"
236+
"github.com/omeid/uconfig/plugins/secret"
237+
238+
"github.com/omeid/uconfig/examples/secrets/secretsource"
218239
)
219240

220241
// Creds is an example of a config struct that uses secret values.
221242
type Creds struct {
222-
// by default, secret plugin will generate a name that is identical
223-
// to env plugin, SCREAM_SNAKE_CASE, so in this case it will be
224-
// APIKEY however, following the standard uConfig nesting rules
225-
// in Config struct below, it becomes CREDS_APIKEY.
226-
APIKey string `secret:""`
227-
// or you can provide your own name, which will not be impacted
228-
// by nesting or the field name.
229-
APIToken string `secret:"API_TOKEN"`
243+
// by default, secret plugin will generate a name that is identical
244+
// to env plugin, SCREAM_SNAKE_CASE, so in this case it will be
245+
// APIKEY however, following the standard uConfig nesting rules
246+
// in Config struct below, it becomes CREDS_APIKEY.
247+
APIKey string `secret:""`
248+
// or you can provide your own name, which will not be impacted
249+
// by nesting or the field name.
250+
APIToken string `secret:"API_TOKEN"`
230251
}
231252

232253
type Config struct {
233-
Creds Creds
234-
}
235-
236-
var files = uconfig.Files{
237-
{"config.json", json.Unmarshal, false},
254+
Creds Creds
238255
}
239256

240257
var secrets = secret.New(func(name string) (string, error) {
241-
// you're free to grab the secret based on the name from wherever
242-
// you please, aws secrets-manager, hashicorp vault, or wherever.
243-
value, ok := secretsource.Get(name)
244-
if !ok {
245-
return "", secret.ErrSecretNotFound
246-
}
247-
248-
return value, nil
258+
// you're free to grab the secret based on the name from wherever
259+
// you please, aws secrets-manager, hashicorp vault, or wherever.
260+
value, ok := secretsource.Get(name)
261+
if !ok {
262+
return "", secret.ErrSecretNotFound
263+
}
264+
265+
return value, nil
249266
})
250267

251268
func main() {
252-
// then you can use the secretPlugin with uConfig like any other plugin.
253-
// Lucky, uconfig.Classic allows passing more plugins, which means
254-
// you can simply do the following for flags, envs, files, and secrets!
255-
conf := uconfig.Classic[Config](files, secrets).Run()
269+
// then you can use the secretPlugin with uConfig like any other plugin.
270+
// Lucky, uconfig.Classic allows passing more plugins, which means
271+
// you can simply do the following for flags, envs, files, and secrets!
272+
conf := uconfig.Classic[Config](nil, secrets).Run()
256273

257-
fmt.Printf("we got an API Key: %s\n", conf.Creds.APIKey)
274+
fmt.Printf("we got an API Key: %s\n", conf.Creds.APIKey)
258275
}
259276
```
260277

examples/sample/main.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,14 @@ type Config struct {
1818

1919
Redis redis.Config
2020
Database database.Config
21+
22+
// the flags plugin allows capturing a single Command after the flags.
23+
// so you can run myprogram -flag=value -s -blah=bleh stop|start|stop and so on.
24+
Mode string `default:"start" flag:",command" usage:"run|start|stop"`
2125
}
2226

2327
var files = uconfig.Files{
28+
{Path: "/etc/demo-app/config.json", Unmarshal: json.Unmarshal, Optional: true},
2429
{Path: "config.json", Unmarshal: json.Unmarshal, Optional: true},
2530
// or short form {"config.json", json.Unmarshal, true},
2631
// And, of course, you can of course add as many files

plugins/file/file.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,11 @@ func New(path string, unmarshal Unmarshal, config Config) plugins.Plugin {
7777
return plug
7878
}
7979

80+
type Plugin interface {
81+
plugins.Plugin
82+
FilePath() string
83+
}
84+
8085
type walker struct {
8186
filepath string
8287
src io.Reader
@@ -86,6 +91,10 @@ type walker struct {
8691
err error
8792
}
8893

94+
func (w *walker) FilePath() string {
95+
return w.filepath
96+
}
97+
8998
func (w *walker) Walk(conf any) error {
9099
if w.err != nil {
91100
return w.err

plugins/flag/flag.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,12 @@ func (f *fieldFlag) IsBoolFlag() bool {
8585
return reflect.ValueOf(f.Field.Interface()).Kind() == reflect.Bool
8686
}
8787

88+
// Indicates whatever the field is "command" field.
89+
// Used by usage and maybe used for other plugins to exclude command.
90+
func IsCommand(f flat.Field) bool {
91+
return f.Meta()[tag] == commandFieldName
92+
}
93+
8894
func (v *visitor) Visit(fields flat.Fields) error {
8995
v.fields = fields
9096

usage.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import (
44
"fmt"
55
"io"
66
"os"
7+
"path"
78
"sort"
89
"strings"
910
"text/tabwriter"
1011

1112
"github.com/omeid/uconfig/flat"
1213
"github.com/omeid/uconfig/plugins"
14+
"github.com/omeid/uconfig/plugins/file"
15+
"github.com/omeid/uconfig/plugins/flag"
1316
)
1417

1518
const usageTag = "usage"
@@ -28,19 +31,21 @@ func (c *config[C]) Usage() {
2831
headers := getHeaders(c.fields)
2932

3033
w := tabwriter.NewWriter(UsageOutput, 0, 0, 4, ' ', 0)
31-
_, _ = fmt.Fprintf(w, "\nSupported Fields:\n")
34+
_, _ = fmt.Fprintf(w, "Usage:\n\t%s [flags] [command]\n", path.Base(os.Args[0]))
35+
_, _ = fmt.Fprintf(w, "\nConfigurations:\n")
3236
_, _ = fmt.Fprintln(w, strings.ToUpper(strings.Join(headers, "\t")))
3337

3438
dashes := make([]string, len(headers))
3539
for i, f := range headers {
36-
n := len(f)
37-
if n < 5 {
38-
n = 5
39-
}
40+
n := max(len(f), 5)
4041
dashes[i] = strings.Repeat("-", n)
4142
}
4243
_, _ = fmt.Fprintln(w, strings.Join(dashes, "\t"))
4344

45+
sort.SliceStable(c.fields, func(i, j int) bool {
46+
return flag.IsCommand(c.fields[j]) // move command to last.
47+
})
48+
4449
for _, f := range c.fields {
4550

4651
values := make([]string, len(headers))
@@ -52,6 +57,23 @@ func (c *config[C]) Usage() {
5257
}
5358

5459
_, _ = fmt.Fprintln(w, strings.Join(values, "\t"))
60+
61+
}
62+
63+
files := []string{}
64+
65+
for _, p := range c.plugins {
66+
if p, ok := p.(file.Plugin); ok {
67+
files = append(files, p.FilePath())
68+
}
69+
}
70+
71+
if len(files) > 0 {
72+
_, _ = fmt.Fprintf(w, "\nConfiguration Files:\n")
73+
for _, fp := range files {
74+
_, _ = fmt.Fprintf(w, "\t%s\n", fp)
75+
}
76+
5577
}
5678

5779
err := w.Flush()

usage_test.go

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package uconfig_test
22

33
import (
44
"bytes"
5+
"encoding/json"
56
"testing"
67

78
"github.com/google/go-cmp/cmp"
@@ -12,11 +13,12 @@ import (
1213
"github.com/omeid/uconfig/plugins/secret"
1314
)
1415

15-
const expectedUsageMessage = `
16-
Supported Fields:
16+
const expectedUsageMessage = `Usage:
17+
uconfig.test [flags] [command]
18+
19+
Configurations:
1720
FIELD FLAG ENV DEFAULT GOODPLUGIN SECRET USAGE
1821
----- ----- ----- ------- ---------- ------ -----
19-
Command [command] COMMAND run Command
2022
Version -version VERSION Version
2123
GoHard -gohard GOHARD GoHard
2224
Redis.Address -redis-address REDIS_ADDRESS Redis.Address
@@ -25,6 +27,11 @@ Rethink.Host.Address -rethink-host-address RETHINK_HOST_ADDRESS
2527
Rethink.Host.Port -rethink-host-port RETHINK_HOST_PORT Rethink.Host.Port
2628
Rethink.Db -rethink-db RETHINK_DB primary Rethink.Db main database used by our application
2729
Rethink.Password -rethink-password RETHINK_PASSWORD Rethink.Password RETHINK_PASSWORD
30+
Command [command] COMMAND run Command
31+
32+
Configuration Files:
33+
/etc/app/config.yaml
34+
config.json
2835
`
2936

3037
type UselessPluginVisitor struct {
@@ -41,6 +48,11 @@ func (*UselessPluginVisitor) Visit(fields flat.Fields) error {
4148
return nil
4249
}
4350

51+
var files = uconfig.Files{
52+
{Path: "/etc/app/config.yaml", Unmarshal: json.Unmarshal, Optional: true},
53+
{Path: "config.json", Unmarshal: json.Unmarshal, Optional: true}, // just for testing of file listing.
54+
}
55+
4456
func TestUsage(t *testing.T) {
4557
var stdout bytes.Buffer
4658
uconfig.UsageOutput = &stdout
@@ -57,7 +69,7 @@ func TestUsage(t *testing.T) {
5769

5870
secretProvider := func(name string) (string, error) { return "top secret token", nil }
5971

60-
conf := uconfig.Classic[f.Config](nil, secret.New(secretProvider), noopPlugin)
72+
conf := uconfig.Classic[f.Config](files, secret.New(secretProvider), noopPlugin)
6173
_, err := conf.Parse()
6274
if err != nil {
6375
t.Fatal(err)

0 commit comments

Comments
 (0)