Derex.dev

Flags in Go

Introduction

In the realm of Go applications, the flag package reigns supreme when it comes to wielding command-line arguments. It empowers you to gracefully accept user-specified instructions, tailoring your program’s behavior on the fly.

Key Functionalities:

  1. Defining Flags: Craft flags using functions like String, Int, Bool, Var and more. Each flag carries a name (short and long versions), a default value, and a usage description.
var name string
var verbose bool

func init() {
  flag.StringVar(&name, "name", "", "Your name (default: empty)")
  flag.BoolVar(&verbose, "verbose", false, "Enable verbose output")
}
  1. Parsing Arguments: When your program launches, unleash the flag.Parse() function. It meticulously dissects the command-line arguments, matching them to the meticulously defined flags and assigning the extracted values to their designated variables.
flag.Parse() 
  1. Accessing Values: Once parsing is complete, the flag variables hold the user-provided values, ready to be employed within your program’s logic.
fmt.Println("Hello,", name)
if verbose {
 fmt.Println("Verbose mode enabled!")
}

Making Connections Flexible with Go’s flag Package: A Real-World Example

The flag package empowers you to create command-line programs with dynamic configurations through user-defined flags. We’ll delve into how this code leverages flags to control retry attempts and data for an HTTP request.

type ArrayValue []string

func (s *ArrayValue) String() string {
    return fmt.Sprintf("%v", *s)
}

func (a *ArrayValue) Set(s string) error {
    *a = strings.Split(s, ",")
    return nil
}

func main() {
    retry := flag.Int("retry", -1, "Defines the max retry count")

    var logPrefix string
    flag.StringVar(&logPrefix, "prefix", "", "Logging prefix")

    var arr ArrayValue
    flag.Var(&arr, "array", "Input array to iterate through. ")

    flag.Parse() // required for assigning values

    logger := log.New(os.Stdout, logPrefix, log.Ldate)
    retryCount := 0
    for retryCount < *retry {
        logger.Println("*Retrying connection")
        post()
        logger.Printf("Sending array %v\n", arr)
        retryCount++
    }
}

func makePostRequest(data []byte) ([]byte, error) {
    url := "http://localhost:8080" // Target URL
    req, err := http.NewRequest("POST", url, bytes.NewBuffer(data))
    if err != nil {
        return nil, err
    }
  
    req.Header.Set("Content-Type", "application/json")
    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return nil, err
    }
    defer resp.Body.Close()
    body, err := io.ReadAll(resp.Body)
    if err != nil {
        return nil, err
    }
    return body, nil
}

func post() {
    data := []byte(`{"message": "Hello from Go!"}`)
    responseBody, err := makePostRequest(data)
    if err != nil {
        fmt.Println("Error:", err)
    } else {
        fmt.Println("Response:", string(responseBody))
    }
}

Running the Program with Flags

Let’s see how this code works in action. To retry this request twice we give an integer of 2 and prefix the logs we named “dre,” we can run:

go run main.go -retry 2 -prefix dre

This will output:

dre2024/03/02 *Retrying connection
Response: {"message": "Hello from Go!"}
dre2024/03/02 Sending array []
dre2024/03/02 *Retrying connection
Response: {"message": "Hello from Go!"}
dre2024/03/02 Sending array []

Let’s break down the code’s flow:

  1. Flag Parsing: The program starts by parsing the command-line arguments using flag.Parse(). This allows users to control retry count, logging prefix, and the input array via flags.

  2. Logging Setup: Based on the -prefix flag, a logger object is created using the log package. This ensures informative logs with user-defined prefixes.

  3. Retry Loop: The program enters a loop that continues until the maximum retry count (retry) is reached. Inside the loop:

  4. Making the Request: The post function constructs a POST request to a designated URL (http://localhost:8080). It utilizes a helper function, makePostRequest, which is responsible for building and executing the request.

This example explores a practical example of using the flag package in Go. The flag package empowers you to create command-line programs with dynamic configurations through user-defined flags. We’ll delve into how this code leverages flags to control retry attempts and data for an HTTP request.

Understanding the Code

The code defines a program that attempts to send data via a POST request. However, the magic lies in its configurability through flags.

The ArrayValue struct tackles a specific challenge: handling comma-separated lists of strings from the -array flag. Standard flag functions don’t handle this natively.

The Set method parses the user’s input, splitting the comma-separated string into individual values stored within the ArrayValue slice. The String method provides a user-friendly way to represent the array content when needed (e.g., during logging).

The flag.Var function accepts a value with the following interface, ArrayValue implements this inteface

type Value interface {
    String() string
    Set(string) error
}
  • retry: This flag, defined with flag.Int, allows users to specify the maximum number of retry attempts before giving up.

  • prefix: Defined with flag.StringVar, this flag lets users set a prefix for log messages, adding context to their output.

  • array: This custom flag utilizes flag.Var and a ArrayValue struct to accept a comma-separated list of strings as input.

The flag.Parse() function is crucial, as it interprets command-line arguments and assigns them to the corresponding flags.

Did I make a mistake? Please consider sending a pull request .