Skip to content

Commit

Permalink
adds support for urfave/cli/v2
Browse files Browse the repository at this point in the history
  • Loading branch information
joicemjoseph committed Nov 5, 2024
1 parent cec6d28 commit 7f2e98d
Show file tree
Hide file tree
Showing 4 changed files with 227 additions and 0 deletions.
118 changes: 118 additions & 0 deletions providers/cliflag/cliflag.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Package cliflag implements a koanf.Provider that reads commandline
// parameters as conf maps using ufafe/cli flag.
package cliflag

import (
"strings"

"github.com/knadh/koanf/maps"
"github.com/urfave/cli/v2"
)

// CliFlag implements a cli.Flag command line provider.
type CliFlag struct {
ctx *cli.Context
delim string
}

// Provider returns a commandline flags provider that returns
// a nested map[string]interface{} of environment variable where the
// nesting hierarchy of keys are defined by delim. For instance, the
// delim "." will convert the key `parent.child.key: 1`
// to `{parent: {child: {key: 1}}}`.
func Provider(f *cli.Context, delim string) *CliFlag {
return &CliFlag{
ctx: f,
delim: delim,
}
}

// Read reads the flag variables and returns a nested conf map.
func (p *CliFlag) Read() (map[string]interface{}, error) {
out := make(map[string]interface{})

// Get command lineage (from root to current command)
lineage := p.ctx.Lineage()
if len(lineage) > 0 {
// Build command path and process flags for each level
var cmdPath []string
for i := len(lineage) - 1; i >= 0; i-- {
cmd := lineage[i]
if cmd.Command == nil {
continue
}
cmdPath = append(cmdPath, cmd.Command.Name)
prefix := strings.Join(cmdPath, p.delim)
p.processFlags(cmd.Command.Flags, prefix, out)
}
}

if p.delim == "" {
return out, nil
}

return maps.Unflatten(out, p.delim), nil
}

func (p *CliFlag) processFlags(flags []cli.Flag, prefix string, out map[string]interface{}) {
for _, flag := range flags {
name := flag.Names()[0]
if p.ctx.IsSet(name) {
value := p.getFlagValue(name)
if value != nil {
// Build the full path for the flag
fullPath := name
if prefix != "global" {
fullPath = prefix + p.delim + name
}

p.setNestedValue(fullPath, value, out)
}
}
}
}

// setNestedValue sets a value in the nested configuration structure
func (p *CliFlag) setNestedValue(path string, value interface{}, out map[string]interface{}) {
parts := strings.Split(path, p.delim)
current := out

// Navigate/create the nested structure
for i := 0; i < len(parts)-1; i++ {
if _, exists := current[parts[i]]; !exists {
current[parts[i]] = make(map[string]interface{})
}
current = current[parts[i]].(map[string]interface{})
}

// Set the final value
current[parts[len(parts)-1]] = value
}

// getFlagValue extracts the typed value from the flag
func (p *CliFlag) getFlagValue(name string) interface{} {
switch {
case p.ctx.IsSet(name):
switch {
case p.ctx.String(name) != "":
return p.ctx.String(name)
case p.ctx.StringSlice(name) != nil:
return p.ctx.StringSlice(name)
case p.ctx.Int(name) != 0:
return p.ctx.Int(name)
case p.ctx.Int64(name) != 0:
return p.ctx.Int64(name)
case p.ctx.IntSlice(name) != nil:
return p.ctx.IntSlice(name)
case p.ctx.Float64(name) != 0:
return p.ctx.Float64(name)
case p.ctx.Bool(name):
return p.ctx.Bool(name)
case p.ctx.Duration(name).String() != "0s":
return p.ctx.Duration(name)
default:
return p.ctx.Generic(name)
}
}
return nil
}
65 changes: 65 additions & 0 deletions providers/cliflag/cliflag_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package cliflag

import (
"fmt"
"os"
"testing"

"github.com/stretchr/testify/require"
"github.com/urfave/cli/v2"
)

func TestCliFlag(t *testing.T) {
cliApp := cli.App{
Name: "testing",
Action: func(ctx *cli.Context) error {
p := Provider(ctx, ".")
x, err := p.Read()
require.NoError(t, err)
require.NotEmpty(t, x)

fmt.Printf("x: %v\n", x)

return nil
},
Flags: []cli.Flag{
cli.HelpFlag,
cli.VersionFlag,
&cli.StringFlag{
Name: "test",
Usage: "test flag",
Value: "test",
Aliases: []string{"t"},
EnvVars: []string{"TEST_FLAG"},
},
},
Commands: []*cli.Command{
{
Name: "x",
Description: "yeah yeah testing",
Action: func(ctx *cli.Context) error {
p := Provider(ctx, "")
x, err := p.Read()
require.NoError(t, err)
require.NotEmpty(t, x)
return nil
},
Flags: []cli.Flag{
cli.HelpFlag,
cli.VersionFlag,
&cli.StringFlag{
Name: "lol",
Usage: "test flag",
Value: "test",
Required: true,
EnvVars: []string{"TEST_FLAG"},
},
},
},
},
}

x := append([]string{"testing", "--test", "gf", "x", "--lol", "dsf"}, os.Args...)
err := cliApp.Run(append(x, os.Environ()...))
require.NoError(t, err)
}
20 changes: 20 additions & 0 deletions providers/cliflag/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
module github.com/knadh/koanf/v2/providers/cliflag

go 1.21.5

require (
github.com/knadh/koanf/maps v0.1.1
github.com/stretchr/testify v1.9.0
github.com/urfave/cli/v2 v2.27.5
)

require (
github.com/cpuguy83/go-md2man/v2 v2.0.5 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
24 changes: 24 additions & 0 deletions providers/cliflag/go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
github.com/cpuguy83/go-md2man/v2 v2.0.5 h1:ZtcqGrnekaHpVLArFSe4HK5DoKx1T0rq2DwVB0alcyc=
github.com/cpuguy83/go-md2man/v2 v2.0.5/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/knadh/koanf/maps v0.1.1 h1:G5TjmUh2D7G2YWf5SQQqSiHRJEjaicvU0KpypqB3NIs=
github.com/knadh/koanf/maps v0.1.1/go.mod h1:npD/QZY3V6ghQDdcQzl1W4ICNVTkohC8E73eI2xW4yI=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ=
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/urfave/cli/v2 v2.27.5 h1:WoHEJLdsXr6dDWoJgMq/CboDmyY/8HMMH1fTECbih+w=
github.com/urfave/cli/v2 v2.27.5/go.mod h1:3Sevf16NykTbInEnD0yKkjDAeZDS0A6bzhBH5hrMvTQ=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 h1:gEOO8jv9F4OT7lGCjxCBTO/36wtF6j2nSip77qHd4x4=
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1/go.mod h1:Ohn+xnUBiLI6FVj/9LpzZWtj1/D6lUovWYBkxHVV3aM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

0 comments on commit 7f2e98d

Please sign in to comment.