Hone logo
Hone
Problems

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) *Flag that returns a pointer to a Flag struct. Similarly, implement IntFlag(name, usage string) *Flag and BoolFlag(name, usage string) *Flag.
  • Flag Value Setting: Each Flag struct should have methods Set(value string) for setting the flag's value.
  • Flag Value Retrieval: Each Flag struct should have methods Value() (interface{}, error) to retrieve the flag's value. The return type should be an interface{} to accommodate different flag types. Return an error if the flag is not set.
  • Flag Parsing: A function Parse(args []string) error that 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 Parse function 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 false if not specified.
  • Integer flags should default to 0 if not specified.
  • String flags should default to an empty string if not specified.
  • The Parse function 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:

  1. Identify flags based on their prefixes (- for short flags, -- for long flags).
  2. Set the corresponding flag's value using the Set method.
  3. Advance the index in the args slice to the next unparsed argument.
  4. 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 args slice in Parse will 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 Parse function should have a time complexity of O(n), where n is the number of arguments in the args slice.

Notes

  • Consider using a map to 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
}
Loading editor...
go