Skip to content

Commit 86cf055

Browse files
committed
docs: add simple & advanced usage examples
1 parent 0dd3960 commit 86cf055

File tree

4 files changed

+301
-0
lines changed

4 files changed

+301
-0
lines changed

examples/advanced/main.go

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
package main
2+
3+
import (
4+
"bytes"
5+
"errors"
6+
"fmt"
7+
"io"
8+
"os"
9+
"time"
10+
11+
"github.com/containeroo/dynflags"
12+
flag "github.com/spf13/pflag"
13+
)
14+
15+
// Example version string
16+
var version = "v1.2.3"
17+
18+
// HelpRequested is a custom error to indicate the user requested help or version info.
19+
type HelpRequested struct {
20+
Message string
21+
}
22+
23+
func (e *HelpRequested) Error() string {
24+
return e.Message
25+
}
26+
27+
// Is checks if the target is a HelpRequested error.
28+
func (e *HelpRequested) Is(target error) bool {
29+
_, ok := target.(*HelpRequested)
30+
return ok
31+
}
32+
33+
// parseFlags orchestrates parsing both global flags (with pflag)
34+
// and dynamic flags (with dynflags). It returns any error that
35+
// indicates parsing failed, or HelpRequested for help/version output.
36+
func parseFlags(args []string, version string, output io.Writer) (*flag.FlagSet, *dynflags.DynFlags, error) {
37+
// 1. Setup the global pflag FlagSet
38+
flagSet := setupGlobalFlags()
39+
flagSet.SetOutput(output) // direct usage output here if needed
40+
41+
// 2. Setup dynamic flags
42+
dynFlags := setupDynamicFlags()
43+
dynFlags.SetOutput(output)
44+
dynFlags.SortFlags = true
45+
46+
// 3. Provide custom usage that prints both global and dynamic flags
47+
setupUsage(flagSet, dynFlags)
48+
49+
// 4. Parse the dynamic flags first so we can separate known vs. unknown arguments
50+
if err := dynFlags.Parse(args); err != nil {
51+
return nil, nil, fmt.Errorf("error parsing dynamic flags: %w", err)
52+
}
53+
54+
// Unknown arguments might be pflag or truly unrecognized
55+
unknownArgs := dynFlags.UnknownArgs()
56+
57+
// 5. Parse pflag (the known global flags)
58+
if err := flagSet.Parse(unknownArgs); err != nil {
59+
return nil, nil, fmt.Errorf("error parsing global flags: %w", err)
60+
}
61+
62+
// 6. Handle special flags for help and version
63+
if err := handleSpecialFlags(flagSet, version); err != nil {
64+
return nil, nil, err
65+
}
66+
67+
return flagSet, dynFlags, nil
68+
}
69+
70+
// setupGlobalFlags defines global flags (pflag) for the application
71+
func setupGlobalFlags() *flag.FlagSet {
72+
flagSet := flag.NewFlagSet("advancedExample", flag.ContinueOnError)
73+
flagSet.SortFlags = false
74+
75+
// Some generic global flags:
76+
flagSet.Bool("version", false, "Show version and exit.")
77+
flagSet.BoolP("help", "h", false, "Show help.")
78+
flagSet.Duration("default-interval", 2*time.Second, "Default interval between checks.")
79+
return flagSet
80+
}
81+
82+
// setupDynamicFlags defines dynamic flags for HTTP, ICMP, and TCP as an example
83+
func setupDynamicFlags() *dynflags.DynFlags {
84+
dyn := dynflags.New(dynflags.ContinueOnError)
85+
dyn.Epilog("For more information, see https://github.com/containeroo/dynflags")
86+
dyn.SortGroups = true
87+
dyn.SortFlags = true
88+
89+
// HTTP group
90+
http := dyn.Group("http")
91+
http.String("name", "", "Name of the HTTP checker")
92+
http.String("method", "GET", "HTTP method to use")
93+
http.String("address", "", "HTTP target URL")
94+
http.Duration("interval", 1*time.Second, "Time between HTTP requests (overrides --default-interval if set)")
95+
http.StringSlices("header", nil, "HTTP headers to send")
96+
http.Bool("allow-duplicate-headers", false, "Allow duplicate HTTP headers")
97+
http.String("expected-status-codes", "200", "Expected HTTP status codes")
98+
http.Bool("skip-tls-verify", false, "Skip TLS verification")
99+
http.Duration("timeout", 2*time.Second, "Timeout for HTTP requests")
100+
101+
// ICMP group
102+
icmp := dyn.Group("icmp")
103+
icmp.String("name", "", "Name of the ICMP checker")
104+
icmp.String("address", "", "ICMP target address")
105+
icmp.Duration("interval", 1*time.Second, "Time between ICMP requests (overrides --default-interval if set)")
106+
icmp.Duration("read-timeout", 2*time.Second, "Timeout for ICMP read")
107+
icmp.Duration("write-timeout", 2*time.Second, "Timeout for ICMP write")
108+
109+
// TCP group
110+
tcp := dyn.Group("tcp")
111+
tcp.String("name", "", "Name of the TCP checker")
112+
tcp.String("address", "", "TCP target address")
113+
tcp.Duration("interval", 1*time.Second, "Time between TCP requests (overrides --default-interval if set)")
114+
tcp.Duration("timeout", 2*time.Second, "Timeout for TCP connection")
115+
116+
return dyn
117+
}
118+
119+
// setupUsage sets a custom usage function that prints both pflag and dynflags usage.
120+
func setupUsage(flagSet *flag.FlagSet, dynFlags *dynflags.DynFlags) {
121+
flagSet.Usage = func() {
122+
fmt.Fprintf(flagSet.Output(), "Usage: %s [GLOBAL FLAGS...] [DYNAMIC FLAGS...]\n", flagSet.Name())
123+
fmt.Fprintln(flagSet.Output(), "\nGlobal Flags:")
124+
flagSet.PrintDefaults()
125+
126+
fmt.Fprintln(flagSet.Output(), "\nDynamic Flags:")
127+
dynFlags.PrintDefaults()
128+
}
129+
}
130+
131+
// handleSpecialFlags checks if --help or --version was requested.
132+
func handleSpecialFlags(flagSet *flag.FlagSet, versionStr string) error {
133+
helpFlag := flagSet.Lookup("help")
134+
if helpFlag != nil && helpFlag.Value.String() == "true" {
135+
// Capture usage output into a buffer, then return a HelpRequested error.
136+
buffer := &bytes.Buffer{}
137+
flagSet.SetOutput(buffer)
138+
flagSet.Usage()
139+
return &HelpRequested{Message: buffer.String()}
140+
}
141+
142+
versionFlag := flagSet.Lookup("version")
143+
if versionFlag != nil && versionFlag.Value.String() == "true" {
144+
return &HelpRequested{Message: fmt.Sprintf("%s version %s\n", flagSet.Name(), versionStr)}
145+
}
146+
147+
return nil
148+
}
149+
150+
// main is our entry point, showing how to parse and then use the flags.
151+
func main() {
152+
// We'll pretend our arguments are from the CLI; replace os.Args[1:] in real usage.
153+
args := []string{
154+
"--http.default.name", "HTTP Checker Default",
155+
"--http.default.address", "default.com:80",
156+
"--http.other.name", "HTTP Checker Other",
157+
"--http.other.address", "other.com:443",
158+
"--http.other.method", "POST",
159+
"--icmp.custom.address", "8.8.4.4",
160+
"--tcp.testing.address", "example.com:443",
161+
"--default-interval=5s", // pflag
162+
// "--unknownArg", "someValue", // see how it's handled by dynflags
163+
}
164+
165+
// Optionally redirect usage/errors to something other than os.Stderr if desired
166+
output := os.Stdout
167+
168+
// Parse everything
169+
flagSet, dynFlags, err := parseFlags(args, version, output)
170+
if err != nil {
171+
// If the user requested help or version, print the message and exit
172+
var hr *HelpRequested
173+
if errors.As(err, &hr) {
174+
fmt.Fprint(output, hr.Message)
175+
return
176+
}
177+
178+
fmt.Fprintf(output, "Failed to parse flags: %v\n", err)
179+
os.Exit(1)
180+
}
181+
182+
// If we got here, parse succeeded. Let's show what we got.
183+
184+
// 1. Print global flags
185+
fmt.Println("=== Global Flags ===")
186+
defaultInterval, _ := flagSet.GetDuration("default-interval")
187+
fmt.Printf("default-interval: %v\n", defaultInterval)
188+
189+
// 2. Print dynamic flags
190+
parsedGroups := dynFlags.Parsed()
191+
192+
fmt.Println("\n=== Dynamic Flags ===")
193+
for groupName, identifiers := range parsedGroups.Groups() {
194+
fmt.Printf("Group: %s\n", groupName)
195+
for identifier, pg := range identifiers {
196+
fmt.Printf(" Identifier: %s\n", identifier)
197+
for flagKey, value := range pg.Values {
198+
fmt.Printf(" %s: %v\n", flagKey, value)
199+
}
200+
}
201+
}
202+
203+
fmt.Println("\nDone!")
204+
}

examples/simple/main.go

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
package main
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/containeroo/dynflags"
8+
)
9+
10+
func main() {
11+
// 1. Create a new DynFlags instance.
12+
df := dynflags.New(dynflags.ContinueOnError)
13+
14+
// Optional metadata for your CLI help text.
15+
df.Title("My Example CLI")
16+
df.Description("This application demonstrates how to use dynflags in a simple program.")
17+
df.Epilog("For more information, visit https://example.com.")
18+
19+
// 2. Define a group named "app" and add a flag called "msg".
20+
// The third parameter here is the default value, and the fourth is the help text.
21+
appGroup := df.Group("app")
22+
appGroup.String("msg", "Hello, World!", "Message to be displayed.")
23+
24+
// For demonstration, we hard-code example arguments.
25+
// In a real program, you would typically use: args := os.Args[1:]
26+
args := []string{
27+
"--app.default.msg", "Hello default DynFlags!",
28+
"--app.custom.msg", "Hello custom DynFlags!",
29+
}
30+
31+
// 3. Parse the command-line arguments.
32+
if err := df.Parse(args); err != nil {
33+
fmt.Fprintf(os.Stderr, "Error parsing flags: %v\n", err)
34+
os.Exit(1)
35+
}
36+
37+
// If no arguments were provided, show usage and exit.
38+
if len(args) < 2 {
39+
df.Usage()
40+
os.Exit(0)
41+
}
42+
43+
// 4. Access the parsed values.
44+
parsedGroups := df.Parsed()
45+
46+
// Look up the "app" group if it was populated.
47+
appParsed := parsedGroups.Lookup("app")
48+
if appParsed == nil {
49+
fmt.Println("No 'app' flags found.")
50+
return
51+
}
52+
53+
// Iterate over all groups returned by df.Parsed().Groups() (in this case, only "app")
54+
// and then over each identifier (e.g., "default", "custom") within that group.
55+
for groupName, identifiers := range parsedGroups.Groups() {
56+
for identifierName, parsedGroup := range identifiers {
57+
msg, err := parsedGroup.GetString("msg") // Custom method you may have added
58+
if err != nil {
59+
fmt.Printf("Error getting flag 'msg' in group %q (identifier %q): %v\n", groupName, identifierName, err)
60+
continue
61+
}
62+
fmt.Printf("Group %q, identifier %q => msg: %q\n", groupName, identifierName, msg)
63+
}
64+
}
65+
66+
// If any arguments were unrecognized or invalid, print them here.
67+
unparsed := df.UnknownArgs()
68+
if len(unparsed) > 0 {
69+
fmt.Println("Unknown arguments:", unparsed)
70+
}
71+
}

go.mod

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
module github.com/containeroo/dynflags
2+
3+
go 1.23.5
4+
5+
require (
6+
github.com/spf13/pflag v1.0.6
7+
github.com/stretchr/testify v1.10.0
8+
)
9+
10+
require (
11+
github.com/davecgh/go-spew v1.1.1 // indirect
12+
github.com/pmezard/go-difflib v1.0.0 // indirect
13+
gopkg.in/yaml.v3 v3.0.1 // indirect
14+
)

go.sum

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
2+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
3+
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
4+
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
5+
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
6+
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
7+
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
8+
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
9+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
10+
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
11+
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
12+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 commit comments

Comments
 (0)