thrawn01/args

View on GitHub
examples/chicken-cli/checkin-cli.go

Summary

Maintainability
A
35 mins
Test Coverage
package main

import (
    "fmt"
    "net/http"
    "net/url"
    "os"

    "bytes"
    "encoding/json"
    "io/ioutil"

    "path"

    "github.com/pkg/errors"
    "github.com/thrawn01/args"
)

type SharedStruct struct {
    Metadata string
    Url      *url.URL
}

func main() {
    parser := args.NewParser(args.Name("http-client"),
        args.Desc("Example http client client"))

    parser.AddOption("--verbose").Alias("-v").Count().
        Help("Be verbose")
    parser.AddOption("--endpoint").Default("http://localhost:1234").Env("API_ENDPOINT").
        Help("The HTTP endpoint our client will talk too")

    parser.AddCommand("super-chickens", func(subParser *args.ArgParser, data interface{}) (int, error) {
        subParser.AddCommand("create", createChickens)
        subParser.AddCommand("list", listChickens)
        subParser.AddCommand("delete", deleteChickens)

        // Apply our super metadata =)
        shared := data.(*SharedStruct)
        shared.Metadata = "super"

        // Run the sub-commands
        return subParser.ParseAndRun(nil, data)
    })

    // Add our non super chicken actions
    parser.AddCommand("create", createChickens)
    parser.AddCommand("list", listChickens)
    parser.AddCommand("delete", deleteChickens)

    // Parse the command line
    opts, err := parser.Parse(nil)
    if err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(1)
    }

    // Build our url from what the user passed in
    parts, err := url.Parse(opts.String("endpoint"))
    if err != nil {
        fmt.Fprint(os.Stderr, "url endpoint '%s' is invalid", opts.String("endpoint"), err.Error())
        os.Exit(1)
    }
    // This is a shows how you can pass in arbitrary struct
    // data to your sub-commands allows you to share data and resources
    // with sub-commands with out resorting to global variables
    super := &SharedStruct{Metadata: "", Url: parts}

    // Run the command chosen by the user
    retCode, err := parser.RunCommand(super)
    if err != nil {
        fmt.Fprintln(os.Stderr, err.Error())
        os.Exit(1)
    }
    os.Exit(retCode)
}

func createChickens(subParser *args.ArgParser, data interface{}) (int, error) {
    subParser.AddArgument("name").Required().Help("The name of the chicken to create")
    opts := subParser.ParseSimple(nil)
    if opts == nil {
        return 1, nil
    }

    shared := data.(*SharedStruct)

    // Create the payload
    payload, err := json.Marshal(map[string]string{
        "name":     opts.String("name"),
        "metadata": shared.Metadata,
    })
    if err != nil {
        return 1, errors.Wrap(err, "while marshalling JSON")
    }

    // Create the new Request
    req, err := http.NewRequest("POST", joinUrl(shared, "chickens"), bytes.NewBuffer(payload))
    if err != nil {
        fmt.Fprintln(os.Stderr, err)
        return 1, errors.Wrap(err, "while creating new http request")
    }
    resp, err := sendRequest(opts, req, &payload)
    if err != nil {
        return 1, err
    }
    fmt.Println(resp)
    return 0, nil
}

func listChickens(subParser *args.ArgParser, data interface{}) (int, error) {
    opts := subParser.GetOpts()

    shared := data.(*SharedStruct)
    req, err := http.NewRequest("GET", joinUrl(shared, "chickens"), nil)
    if err != nil {
        return 1, errors.Wrap(err, "while creating new http request")
    }
    resp, err := sendRequest(opts, req, nil)
    if err != nil {
        return 1, err
    }
    fmt.Println(resp)
    return 0, nil
}

func deleteChickens(subParser *args.ArgParser, data interface{}) (int, error) {
    subParser.AddArgument("name").Required().Help("The name of the chicken to delete")
    opts := subParser.ParseSimple(nil)
    if opts == nil {
        return 1, nil
    }

    shared := data.(*SharedStruct)
    req, err := http.NewRequest("DELETE", joinUrl(shared, "chickens", opts.String("name")), nil)
    if err != nil {
        return 1, errors.Wrap(err, "while creating new http request")
    }
    resp, err := sendRequest(opts, req, nil)
    if err != nil {
        return 1, err
    }
    fmt.Println(resp)
    return 0, nil
}

func joinUrl(shared *SharedStruct, slugs ...string) string {
    uri := shared.Url
    results := []string{shared.Url.Path}
    for _, slug := range slugs {
        results = append(results, slug)
    }
    uri.Path = path.Join(slugs...)
    return uri.String()
}

func sendRequest(opts *args.Options, req *http.Request, payload *[]byte) (string, error) {
    req.Header.Set("Content-Type", "application/json")

    // Print a curl representation of the request if verbose
    curl := args.CurlString(req, payload)
    if opts.Bool("verbose") {
        fmt.Printf("-- %s\n", curl)
    }

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        return "", errors.Wrap(err, "Client Error")
    }
    defer resp.Body.Close()

    // Read in the entire response
    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        return "", errors.Wrap(err, "ReadAll Error")
    }
    return string(body), nil
}