3
\$\begingroup\$

Reasons

I'm recently learning Go with various exercises (might be more questions coming in the future)

However, I'm quite annoyed with the default argument parsing module in Go flag as it doesn't satisfy my needs most of the time.

So as a learning exercise I went ahead and created a simplified clone of python's argparse module. It isn't a 1 on 1 clone and I don't want it to be.

Module

package argparse

import (
    "fmt"
    "os"
    "reflect"
    "strconv"
    "strings"
)

type Argument struct {
    ShortFlag, LongFlag, Help string
    Type, Default interface{}
    Required bool
}

type Argparse struct {
    Programme, Description string
    Arguments []Argument
}

func (p *Argparse) findArgument(arg string) *Argument {
    if !strings.Contains(arg, "-") {
        return nil
    }

    for _, a := range p.Arguments {
        if a.ShortFlag == arg[1:] || a.LongFlag == arg[2:] {
            return &a
        }
    }
    return nil
}

func (p *Argparse) Parse() map[string]interface{}  {
    args := make(map[string]interface{})
    osArgs := os.Args

    for _, a := range osArgs[1:] {
        if "-h" == a || "--help" == a {
            p.Help()
        }
    }

    for i := 1; i < len(osArgs); i++ {
        a := p.findArgument(osArgs[i])
        if a != nil {
            _type := reflect.TypeOf(a.Type)
            switch _type.Kind() {
                case reflect.String:
                    if len(osArgs) == i + 1 {
                        fmt.Println("[ERROR] Didn't supply the argument for", a.LongFlag)
                        os.Exit(3)
                    }
                    args[a.LongFlag] = osArgs[i + 1]
                    i += 1
                case reflect.Int:
                    if len(osArgs) == i + 1 {
                        fmt.Println("[ERROR] Didn't supply the argument for", a.LongFlag)
                        os.Exit(3)
                    }                   
                    retValue, err := strconv.Atoi(osArgs[i + 1])
                    if err != nil {
                        fmt.Println(osArgs[i + 1], "is not an integer")
                        os.Exit(3)
                    }
                    args[a.LongFlag] = retValue
                    i += 1
                case reflect.Bool:
                    args[a.LongFlag] = true
                default:
                    fmt.Println("[ERROR]: Flag", a.LongFlag, "is of type", _type.Kind(), "which is not supported")
                    os.Exit(3)
            }
        }
    }

    for _, arg := range p.Arguments {
        if _, f := args[arg.LongFlag]; !f {
            if arg.Required && arg.Default == nil {
                fmt.Println("[ERROR]: Flag", arg.LongFlag, "is required and not set")
                os.Exit(3)
            }

            args[arg.LongFlag] = arg.Default
        }
    }
    return args
}

func (p *Argparse) AddArgument(arg Argument) {
    if arg.LongFlag == "" {
        fmt.Println("[ERROR]: LongFlag must be set")
        os.Exit(1)
    }
    if arg.Type == nil {
        fmt.Println("[ERROR]: Type must be set")
        os.Exit(1)
    }

    p.Arguments = append(p.Arguments, arg)
}

func (p *Argparse) Help() {
    if p.Programme == "" {
        p.Programme = os.Args[0]
    }

    fmt.Print("Usage ", p.Programme, "\n\n")
    fmt.Print(p.Description, "\n\n")
    for _, a := range p.Arguments {
        fmt.Print("\t", "-" + a.ShortFlag, ", ", "--" + a.LongFlag, "\t", a.Help, "\n")
    }
    os.Exit(0)
}

Implementation

package main

import (
    "fmt"
    "argparse"
)

func parseArguments() map[string]interface{} {
    parser := argparse.Argparse {
        Description: "Testing argparse logic",
    }

    parser.AddArgument(
        argparse.Argument {
            ShortFlag: "t", LongFlag: "text", Type: "string", 
            Required: true, Help: "A line of text",
        },
    )

    parser.AddArgument(
        argparse.Argument {
            ShortFlag: "e", LongFlag: "enc", Type: false, 
            Help: "Something else", Default: false,
        },
    )

    return parser.Parse()
}

func main() {
    args := parseArguments()
    for k, v := range args {
        fmt.Println("Argument:", k, "=", v)
    }
}

Example

$ ./test -t "-e"
Argument: text = -e
Argument: enc = false

As mentioned I'm fairly new to the language so any recommendation would be greatly appreciated!

\$\endgroup\$
1

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.