Listing 5.3: Setting FlagSet's output

June 24, 2025 ยท View on GitHub

What's changed?

Tip

You can copy and directly git-apply this diff to your local copy of the code.

--- a/hit/cmd/hit/config.go
+++ b/hit/cmd/hit/config.go
@@ -3,7 +3,8 @@
 import (
 	"errors"
 	"flag"
 	"fmt"
+	"io"
 	"net/url"
 	"strconv"
 )
@@ -18,24 +19,26 @@
-func parseArgs(c *config, args []string) error {
+func parseArgs(c *config, args []string, stderr io.Writer) error {
 	fs := flag.NewFlagSet("hit", flag.ContinueOnError)
+	fs.SetOutput(stderr)
+
 	fs.Usage = func() {
 		fmt.Fprintf(fs.Output(), "usage: %s [options] url\n", fs.Name())
 		fs.PrintDefaults()
 	}
 
 	fs.Var(asPositiveIntValue(&c.n), "n", "Number of requests")
 	fs.Var(asPositiveIntValue(&c.c), "c", "Concurrency level")
 	fs.Var(asPositiveIntValue(&c.rps), "rps", "Requests per second")
 
 	if err := fs.Parse(args); err != nil {
 		return err
 	}
 	c.url = fs.Arg(0)
 
 	if err := validateArgs(c); err != nil {
 		fmt.Fprintln(fs.Output(), err)
 		fs.Usage()
 		return err
 	}
 
 	return nil
 }

Code in the file

Tip

Click the links to see the file and its directory in their original locations and state as they were at the time of the listing.

hit / cmd / hit / config.go

package main

import (
	"errors"
	"flag"
	"fmt"
	"io"
	"net/url"
	"strconv"
)

type config struct {
	url string
	n   int
	c   int
	rps int
}

func parseArgs(c *config, args []string, stderr io.Writer) error {
	fs := flag.NewFlagSet("hit", flag.ContinueOnError)
	fs.SetOutput(stderr)

	fs.Usage = func() {
		fmt.Fprintf(fs.Output(), "usage: %s [options] url\n", fs.Name())
		fs.PrintDefaults()
	}

	fs.Var(asPositiveIntValue(&c.n), "n", "Number of requests")
	fs.Var(asPositiveIntValue(&c.c), "c", "Concurrency level")
	fs.Var(asPositiveIntValue(&c.rps), "rps", "Requests per second")

	if err := fs.Parse(args); err != nil {
		return err
	}
	c.url = fs.Arg(0)

	if err := validateArgs(c); err != nil {
		fmt.Fprintln(fs.Output(), err)
		fs.Usage()
		return err
	}

	return nil
}

func validateArgs(c *config) error {
	u, err := url.Parse(c.url)
	if err != nil {
		return fmt.Errorf("invalid value %q for url: %w", c.url, err)
	}
	if c.url == "" || u.Host == "" || u.Scheme == "" {
		return fmt.Errorf("invalid value %q for url: requires a valid url", c.url)
	}
	if c.n < c.c {
		return fmt.Errorf("invalid value %d for flag -n: should be greater than flag -c: %d", c.n, c.c)
	}
	return nil
}

type positiveIntValue int

func asPositiveIntValue(p *int) *positiveIntValue {
	return (*positiveIntValue)(p)
}

func (n *positiveIntValue) String() string {
	return strconv.Itoa(int(*n))
}

func (n *positiveIntValue) Set(s string) error {
	v, err := strconv.ParseInt(s, 0, strconv.IntSize)
	if err != nil {
		return err
	}
	if v <= 0 {
		return errors.New("should be greater than zero")
	}
	*n = positiveIntValue(v)

	return nil
}