Command-Line Flag Parsing with Go's flag Package
This challenge focuses on implementing a simplified version of Go's flag package. Command-line flags are essential for creating flexible and configurable applications, allowing users to customize behavior without modifying the source code. Your task is to build a basic flag parser that handles string, integer, and boolean flags.
Problem Description
You need to implement a Flag package that allows users to define and parse command-line flags. The package should support the following:
- Flag Definition: A function
StringFlag(name, usage string) *Flagthat returns a pointer to aFlagstruct. Similarly, implementIntFlag(name, usage string) *FlagandBoolFlag(name, usage string) *Flag. - Flag Value Setting: Each
Flagstruct should have methodsSet(value string)for setting the flag's value. - Flag Value Retrieval: Each
Flagstruct should have methodsValue() (interface{}, error)to retrieve the flag's value. The return type should be aninterface{}to accommodate different flag types. Return an error if the flag is not set. - Flag Parsing: A function
Parse(args []string) errorthat takes a slice of strings (command-line arguments) and parses the flags. The function should iterate through the arguments, identify flags, and set their values accordingly. Flags should be recognized by their names (e.g.,-name value). Arguments should be consumed as they are parsed. - Error Handling: The
Parsefunction should return an error if there are issues during parsing, such as invalid flag values or missing values for required flags.
Key Requirements:
- The package should handle short flags (e.g.,
-n) and long flags (e.g.,--name). - Flags with values should be parsed correctly.
- Boolean flags should default to
falseif not specified. - Integer flags should default to 0 if not specified.
- String flags should default to an empty string if not specified.
- The
Parsefunction should consume arguments as it parses them. Arguments that are not flags should be ignored.
Expected Behavior:
When Parse is called with a slice of strings, it should:
- Identify flags based on their prefixes (
-for short flags,--for long flags). - Set the corresponding flag's value using the
Setmethod. - Advance the index in the
argsslice to the next unparsed argument. - Return an error if a flag is missing a value or if the value is invalid for the flag's type.
Examples
Example 1:
Input: []string{"-n", "example", "-v"}
Output: nil
Explanation: The `-n` flag is set to "example", and the `-v` flag is set to its default boolean value (false).
Example 2:
Input: []string{"--name", "example", "-v", "true"}
Output: nil
Explanation: The `--name` flag is set to "example", and the `-v` flag is set to true.
Example 3:
Input: []string{"-n"}
Output: error: missing value for flag -n
Explanation: The `-n` flag is missing a value.
Example 4:
Input: []string{"-n", "abc", "-i", "xyz"}
Output: error: invalid value for flag -i: "xyz" is not an integer
Explanation: The value "xyz" is not a valid integer for the `-i` flag.
Constraints
- The
argsslice inParsewill contain at most 100 elements. - Flag names will be between 1 and 32 characters long.
- Flag values will be strings. Integer flags will accept strings that can be parsed into integers. Boolean flags will accept "true" or "false" (case-insensitive).
- The
Parsefunction should have a time complexity of O(n), where n is the number of arguments in theargsslice.
Notes
- Consider using a
mapto store the defined flags for efficient lookup during parsing. - Implement robust error handling to provide informative error messages to the user.
- Focus on the core functionality of flag parsing. You don't need to implement advanced features like flag dependencies or default values beyond the simple defaults described above.
- The
Value()method should return an error if the flag hasn't been set.
package flag
import (
"fmt"
"strconv"
"strings"
)
type Flag interface {
Set(value string)
Value() (interface{}, error)
}
type flagStruct struct {
name string
usage string
value interface{}
isString bool
isInt bool
isBool bool
}
func StringFlag(name, usage string) *flagStruct {
return &flagStruct{
name: name,
usage: usage,
value: "",
isString: true,
}
}
func IntFlag(name, usage string) *flagStruct {
return &flagStruct{
name: name,
usage: usage,
value: 0,
isInt: true,
}
}
func BoolFlag(name, usage string) *flagStruct {
return &flagStruct{
name: name,
usage: usage,
value: false,
isBool: true,
}
}
func (f *flagStruct) Set(value string) {
f.value = value
}
func (f *flagStruct) Value() (interface{}, error) {
if f.value == nil {
return nil, fmt.Errorf("flag %s is not set", f.name)
}
if f.isInt {
intValue, err := strconv.Atoi(f.value.(string))
if err != nil {
return nil, fmt.Errorf("invalid value for flag %s: %s is not an integer", f.name, f.value.(string))
}
return intValue, nil
}
if f.isBool {
stringValue := strings.ToLower(f.value.(string))
if stringValue == "true" {
return true, nil
} else if stringValue == "false" {
return false, nil
} else {
return nil, fmt.Errorf("invalid value for flag %s: %s is not a boolean", f.name, f.value.(string))
}
}
return f.value, nil
}
func Parse(args []string) error {
flagMap := make(map[string]*flagStruct)
i := 0
for i < len(args) {
if strings.HasPrefix(args[i], "-") && len(args[i]) > 1 {
shortFlag := args[i][1:]
if i+1 < len(args) {
flagMap[shortFlag].Set(args[i+1])
i += 2
} else {
return fmt.Errorf("missing value for flag %s", args[i])
}
} else if strings.HasPrefix(args[i], "--") && len(args[i]) > 2 {
longFlag := args[i][2:]
if i+1 < len(args) {
flagMap[longFlag].Set(args[i+1])
i += 2
} else {
return fmt.Errorf("missing value for flag %s", args[i])
}
} else {
i++ // Ignore non-flag arguments
}
}
return nil
}