Skip to content

neiser/go-nagini

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

49 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Nagini - Fluent and generic API for Cobra

coverage Go Report Card Go Reference pre-commit

Nagini wraps the famous Cobra CLI library with a fluent API and Go generics.

It supports slice values (comma-separated values) and arbitrary parsing.

It optionally binds flags to the Viper configuration library.

Installation

go get github.com/neiser/go-nagini

Example Usage

Simple command with generic, string-like flag and boolean flag

Showing examples/simple/main.go:

package main

import (
  "log"

  "github.com/neiser/go-nagini/command"
  "github.com/neiser/go-nagini/flag"
)

type (
  Wand string
)

func main() {
  var (
    myName       string
    favoriteWand Wand = "elder"
    iAmVoldemort bool
  )
  _ = command.New().
    Flag(flag.String(&myName, flag.NotEmpty), flag.RegisterOptions{
      Name:     "my-name",
      Required: true,
    }).
    Flag(flag.String(&favoriteWand, flag.NotEmptyTrimmed), flag.RegisterOptions{
      Name:  "favorite-wand",
      Usage: "Specify magic wand",
    }).
    Flag(flag.Bool(&iAmVoldemort), flag.RegisterOptions{
      Name: "i-am-voldemort",
    }).
    Run(func() error {
      if iAmVoldemort {
        return command.WithExitCodeError{ExitCode: 66}
      }
      log.Printf("I'm %s and my favorite wand is '%s'", myName, favoriteWand)
      return nil
    }).
    Execute()
}

Run with

go run ./examples/simple --my-name Harry

Command with slice flag

Showing examples/slice/main.go:

package main

import (
  "log"
  "strconv"

  "github.com/neiser/go-nagini/command"
  "github.com/neiser/go-nagini/flag"
)

func main() {
  var (
    someInts []int
  )
  _ = command.New().
    Flag(flag.Slice(&someInts, flag.ParseSliceOf(strconv.Atoi)), flag.RegisterOptions{
      Name:     "some-ints",
      Required: true,
    }).
    Run(func() error {
      log.Printf("Got integers: '%v'", someInts)
      return nil
    }).
    Execute()
}

Run with

go run ./examples/slice --some-ints 5,6,7

Adding subcommands with fluent description

Showing examples/subcommand/main.go:

package main

import (
  "errors"
  "log"

  "github.com/neiser/go-nagini/command"
  "github.com/neiser/go-nagini/flag"
)

var ErrCannotUseMagic = errors.New("cannot use magic")

func main() {
  var (
    useMagic bool
  )
  _ = command.New().
    Flag(flag.Bool(&useMagic), flag.RegisterOptions{
      Name:       "use-magic",
      Usage:      "Use some magic, c'mon",
      Persistent: true,
    }).
    AddCommands(
      command.New().
        Use("muggle").
        Short("A person which cannot use magic").
        Run(func() error {
          if useMagic {
            return command.WithExitCodeError{
              ExitCode: 21,
              Wrapped:  ErrCannotUseMagic,
            }
          }
          return nil
        }),
      command.New().
        Use("wizard").
        Short("A person which may use magic").
        Run(func() error {
          if useMagic {
            log.Printf("Abracadabra!")
          }
          return nil
        }),
    ).
    AddPersistentPreRun(func() error {
      log.Printf("Will always run!")
      return nil
    }).
    AddPersistentPreRun(func() error {
      log.Printf("Will also run!")
      return nil
    }).
    Execute()
}

Run with

go run ./examples/subcommand wizard --use-magic
go run ./examples/subcommand muggle --use-magic

Binding a flag to Viper, flag value takes precedence over Viper

Showing examples/viper/main.go:

package main

import (
  "errors"
  "log"

  "github.com/neiser/go-nagini/command"
  "github.com/neiser/go-nagini/flag"
  "github.com/neiser/go-nagini/flag/binding"
  "github.com/spf13/viper"
)

func main() {
  viper.AutomaticEnv() // tell Viper to read env
  var (
    favoriteHouse = "Hufflepuff"
    isEvil        = false
  )
  _ = command.New().
    Flag(
      binding.Viper{
        Value:     flag.String(&favoriteHouse, flag.NotEmptyTrimmed),
        ConfigKey: "FAVORITE_HOUSE",
      },
      flag.RegisterOptions{
        Name: "house",
      },
    ).
    Flag(
      binding.Viper{
        Value:     flag.Bool(&isEvil),
        ConfigKey: "IS_EVIL",
      },
      flag.RegisterOptions{
        Shorthand:  "e",
        Persistent: true,
      },
    ).
    Run(func() error {
      prefix := "Favorite"
      if isEvil {
        prefix = "Evil favorite"
      }
      log.Printf("%s house is %s", prefix, favoriteHouse)
      return nil
    }).
    AddCommands(command.New().Use("secret-chamber").Run(func() error {
      if !isEvil {
        return errors.New("only evil persons can enter")
      }
      return nil
    })).
    Execute()
}

Run with

IS_EVIL=true FAVORITE_HOUSE=Slytherin go run ./examples/viper

or

FAVORITE_HOUSE=Slytherin go run ./examples/viper --house Hufflepuff

or

IS_EVIL=true go run ./examples/viper secret-chamber

Marking groups of flags

Showing examples/mark/main.go:

package main

import (
  "log"

  "github.com/neiser/go-nagini/command"
  "github.com/neiser/go-nagini/flag"
)

func main() {
  var (
    name         = "Harry"
    iAmVoldemort bool
  )
  _ = command.New().
    Flag(flag.String(&name, flag.NotEmpty), flag.RegisterOptions{
      Name: "name",
    }).
    Flag(flag.String(&name, flag.NotEmpty), flag.RegisterOptions{
      Name: "nickname",
    }).
    Flag(flag.Bool(&iAmVoldemort), flag.RegisterOptions{
      Name: "i-am-voldemort",
    }).
    MarkFlagsMutuallyExclusive(&name, &iAmVoldemort).
    Run(func() error {
      switch {
      case iAmVoldemort:
        log.Print("My name is Voldemort!")
      case name != "":
        log.Printf("My name is %s", name)
      }
      return nil
    }).
    Execute()
}

Run with

go run ./examples/mark --i-am-voldemort

or (will fail)

go run ./examples/mark --i-am-voldemort --name "Harry"

Implementing flag.Value for VerboseLevel

Showing examples/verbose/main.go:

package main

import (
  "fmt"
  "log"
  "strconv"

  "github.com/neiser/go-nagini/command"
  "github.com/neiser/go-nagini/flag"
)

type VerboseLevel int

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

func (v *VerboseLevel) Set(s string) error {
  if s == "true" {
    *v++
    return nil
  }
  val, err := strconv.Atoi(s)
  if err != nil {
    return fmt.Errorf("cannot convert: %w", err)
  }
  *v = VerboseLevel(val)
  return nil
}

func (v *VerboseLevel) Type() string {
  return "int"
}

//nolint:ireturn
func (v *VerboseLevel) Target() any {
  return v
}

func (v *VerboseLevel) IsBoolFlag() bool {
  return true
}

func main() {
  var (
    verboseLevel VerboseLevel
    enableDebug  bool
  )
  _ = command.New().
    Flag(&verboseLevel, flag.RegisterOptions{Name: "verbose", Shorthand: "v"}).
    Flag(flag.Bool(&enableDebug), flag.RegisterOptions{Name: "debug"}).
    MarkFlagsMutuallyExclusive(&enableDebug, &verboseLevel).
    Run(func() error {
      log.Printf("Verbose level is %d\n", verboseLevel)
      return nil
    }).
    Execute()
}

Run with

go run ./examples/verbose -vvv

Development and Contributions

Install the provided pre-commit hooks with

pre-commit install

This library only exposes some limited feature set of Cobra. Please open an issue if you really miss something which fits well into this library. Otherwise, you can always modify the embedded *cobra.Command directly as a workaround.

About

Fluent wrapper for the Go CLI library spf13/cobra

Topics

Resources

License

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •