diff --git a/VERSION b/VERSION index 923fd4d..77989f5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.8 +v2.0.9 \ No newline at end of file diff --git a/alias.go b/alias.go new file mode 100644 index 0000000..f422b67 --- /dev/null +++ b/alias.go @@ -0,0 +1,10 @@ +package figtree + +func (tree *figTree) WithAlias(name, alias string) { + tree.mu.Lock() + defer tree.mu.Unlock() + if _, exists := tree.aliases[alias]; exists { + return + } + tree.aliases[alias] = name +} diff --git a/alias_test.go b/alias_test.go new file mode 100644 index 0000000..85e970a --- /dev/null +++ b/alias_test.go @@ -0,0 +1,91 @@ +package figtree + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestWithAlias(t *testing.T) { + const cmdLong, cmdAliasLong, valueLong, usage = "long", "l", "default", "usage" + const cmdShort, cmdAliasShort, valueShort = "short", "s", "default" + + t.Run("basic_usage", func(t *testing.T) { + figs := With(Options{Germinate: true, Tracking: false}) + figs.NewString(cmdLong, valueLong, usage) + figs.WithAlias(cmdLong, cmdAliasLong) + assert.NoError(t, figs.Parse()) + t.Log(figs.Usage()) + + assert.Equal(t, valueLong, *figs.String(cmdLong)) + assert.Equal(t, valueLong, *figs.String(cmdAliasLong)) + figs = nil + }) + + t.Run("multiple_aliases", func(t *testing.T) { + const k, v, u = "name", "yeshua", "the real name of god" + ka1 := "father" + ka2 := "son" + ka3 := "rauch-hokadesch" + figs := With(Options{Germinate: true, Tracking: false}) + figs.NewString(k, v, u) + figs.WithAlias(k, ka1) + figs.WithAlias(k, ka2) + figs.WithAlias(k, ka3) + assert.NoError(t, figs.Parse()) + t.Log(figs.Usage()) + + assert.Equal(t, v, *figs.String(k)) + assert.Equal(t, v, *figs.String(ka1)) + assert.Equal(t, v, *figs.String(ka2)) + assert.Equal(t, v, *figs.String(ka3)) + figs = nil + }) + + t.Run("complex_usage", func(t *testing.T) { + + figs := With(Options{Germinate: true, Tracking: false}) + // long + figs.NewString(cmdLong, valueLong, usage) + figs.WithAlias(cmdLong, cmdAliasLong) + figs.WithValidator(cmdLong, AssureStringNotEmpty) + + // short + figs.NewString(cmdShort, valueShort, usage) + figs.WithAlias(cmdShort, cmdAliasShort) + figs.WithValidator(cmdShort, AssureStringNotEmpty) + + assert.NoError(t, figs.Parse()) + t.Log(figs.Usage()) + // long + assert.Equal(t, valueLong, *figs.String(cmdLong)) + assert.Equal(t, valueLong, *figs.String(cmdAliasLong)) + // short + assert.Equal(t, valueShort, *figs.String(cmdShort)) + assert.Equal(t, valueShort, *figs.String(cmdAliasShort)) + + figs = nil + + }) + + t.Run("alias_with_int", func(t *testing.T) { + figs := With(Options{Germinate: true}) + figs.NewInt("count", 42, "usage") + figs.WithAlias("count", "c") + assert.NoError(t, figs.Parse()) + t.Log(figs.Usage()) + assert.Equal(t, 42, *figs.Int("count")) + assert.Equal(t, 42, *figs.Int("c")) + }) + + t.Run("alias_conflict", func(t *testing.T) { + figs := With(Options{Germinate: true}) + figs.NewString("one", "value1", "usage") + figs.NewString("two", "value2", "usage") + figs.WithAlias("one", "x") + figs.WithAlias("two", "x") // Should this overwrite or be ignored? + assert.NoError(t, figs.Parse()) + assert.Equal(t, "value1", *figs.String("x")) // Clarify expected behavior + t.Log(figs.Usage()) + }) +} diff --git a/figs.go b/figtree.go similarity index 96% rename from figs.go rename to figtree.go index 6e2be35..489873c 100644 --- a/figs.go +++ b/figtree.go @@ -72,10 +72,9 @@ func With(opts Options) Plant { harvest: opts.Harvest, angel: &angel, problems: make([]error, 0), + aliases: make(map[string]string), figs: make(map[string]*figFruit), withered: make(map[string]witheredFig), - sources: make(map[string]SourceConfig), - sourceLocker: sync.RWMutex{}, mu: sync.RWMutex{}, mutationsCh: make(chan Mutation), flagSet: flag.NewFlagSet(os.Args[0], flag.ContinueOnError), diff --git a/figs_test.go b/figtree_test.go similarity index 93% rename from figs_test.go rename to figtree_test.go index e626089..eecfcff 100644 --- a/figs_test.go +++ b/figtree_test.go @@ -139,9 +139,16 @@ func TestGrow(t *testing.T) { } func TestVersion(t *testing.T) { - assert.Empty(t, currentVersion, "currentVersion should return an empty string") - assert.NotEmpty(t, Version(), "Version() should not return an empty string") - assert.Equal(t, currentVersion, Version(), "Version() should return the current version") + t.Run("current_version_default_empty", func(t *testing.T) { + if len(currentVersion) > 0 { + currentVersion = "" + } + assert.Empty(t, currentVersion, "currentVersion should return an empty string") + }) + t.Run("current_version", func(t *testing.T) { + assert.NotEmpty(t, Version(), "Version() should not return an empty string") + assert.Equal(t, currentVersion, Version(), "Version() should return the current version") + }) } func TestIsTracking(t *testing.T) { @@ -262,30 +269,6 @@ func TestIsTracking(t *testing.T) { }) } -/* -func TestTree_PollinateInt(t *testing.T) { - -} -func TestTree_PollinateInt64(t *testing.T) { - -} -func TestTree_PollinateFloat64(t *testing.T) { - -} -func TestTree_PollinateDuration(t *testing.T) { - -} -func TestTree_PollinateUnitDuration(t *testing.T) { - -} -func TestTree_PollinateList(t *testing.T) { - -} -func TestTree_PollinateMap(t *testing.T) { - -} -*/ - func TestTree_PollinateString(t *testing.T) { figs := With(Options{Pollinate: true, Tracking: true, Germinate: true}) figs.NewString("test", "initial", "usage") diff --git a/go.mod b/go.mod index 4c2d193..3d10f4c 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,15 @@ module github.com/andreimerlescu/figtree/v2 go 1.23.4 require ( - github.com/andreimerlescu/checkfs v1.0.3 + github.com/andreimerlescu/checkfs v1.0.4 github.com/go-ini/ini v1.67.0 github.com/stretchr/testify v1.10.0 - golang.org/x/term v0.31.0 + golang.org/x/term v0.32.0 gopkg.in/yaml.v3 v3.0.1 ) require ( github.com/davecgh/go-spew v1.1.1 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect - golang.org/x/sys v0.32.0 // indirect + golang.org/x/sys v0.33.0 // indirect ) diff --git a/go.sum b/go.sum index 0010e9f..9fcc3df 100644 --- a/go.sum +++ b/go.sum @@ -1,7 +1,5 @@ -github.com/andreimerlescu/checkfs v1.0.1 h1:4w4tPgI20NEkVQbhES8GwgJDBuLpPdbdzYgI6blzI/k= -github.com/andreimerlescu/checkfs v1.0.1/go.mod h1:VBk2qYxPz4l8nbLnT2I9LYHJ8ygxRxAJv14dBYJQCrw= -github.com/andreimerlescu/checkfs v1.0.3 h1:vnYAPI+Yu+4YfuvY8Jjnq26p7WhIvw4XjCJ5w3S0sjI= -github.com/andreimerlescu/checkfs v1.0.3/go.mod h1:ADaqjiRJf3gmyENLS3v9bJIaEH00IOeM48cXxVwy1JY= +github.com/andreimerlescu/checkfs v1.0.4 h1:pRXZGW1sfe+yXyWNUxmPC2IiX5yT3vF1V5O8PXulnFc= +github.com/andreimerlescu/checkfs v1.0.4/go.mod h1:ADaqjiRJf3gmyENLS3v9bJIaEH00IOeM48cXxVwy1JY= 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/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= @@ -10,14 +8,10 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= -golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= -golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= -golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= -golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= -golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= +golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= +golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.32.0 h1:DR4lr0TjUs3epypdhTOkMmuF5CDFJ/8pOnbzMZPQ7bg= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= 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= diff --git a/internals_test.go b/internals_test.go index e064697..ec19341 100644 --- a/internals_test.go +++ b/internals_test.go @@ -17,16 +17,15 @@ func TestTree_checkAndSetFromEnv(t *testing.T) { // create a new fig tree var figs *figTree figs = &figTree{ - harvest: 1, - figs: make(map[string]*figFruit), - tracking: false, - withered: make(map[string]witheredFig), - sources: make(map[string]SourceConfig), - sourceLocker: sync.RWMutex{}, - flagSet: flag.NewFlagSet(os.Args[0], flag.ContinueOnError), - mu: sync.RWMutex{}, - mutationsCh: make(chan Mutation, 1), - filterTests: true, + harvest: 1, + figs: make(map[string]*figFruit), + tracking: false, + withered: make(map[string]witheredFig), + aliases: make(map[string]string), + flagSet: flag.NewFlagSet(os.Args[0], flag.ContinueOnError), + mu: sync.RWMutex{}, + mutationsCh: make(chan Mutation, 1), + filterTests: true, } // assign an int to k @@ -63,8 +62,7 @@ func TestTree_setValue(t *testing.T) { ConfigFilePath string figs map[string]*figFruit withered map[string]witheredFig - sources map[string]SourceConfig - sourceLocker sync.RWMutex + aliases map[string]string mu sync.RWMutex tracking bool mutationsCh chan Mutation @@ -83,11 +81,10 @@ func TestTree_setValue(t *testing.T) { { name: "Set int value", fields: fields{ - figs: make(map[string]*figFruit), - withered: make(map[string]witheredFig), - mutationsCh: make(chan Mutation, 1), - sources: make(map[string]SourceConfig), - sourceLocker: sync.RWMutex{}, + figs: make(map[string]*figFruit), + withered: make(map[string]witheredFig), + aliases: make(map[string]string), + mutationsCh: make(chan Mutation, 1), }, args: args{ flagVal: new(int), @@ -99,11 +96,10 @@ func TestTree_setValue(t *testing.T) { { name: "Set string value", fields: fields{ - figs: make(map[string]*figFruit), - withered: make(map[string]witheredFig), - mutationsCh: make(chan Mutation, 1), - sources: make(map[string]SourceConfig), - sourceLocker: sync.RWMutex{}, + figs: make(map[string]*figFruit), + withered: make(map[string]witheredFig), + aliases: make(map[string]string), + mutationsCh: make(chan Mutation, 1), }, args: args{ flagVal: new(string), @@ -115,11 +111,10 @@ func TestTree_setValue(t *testing.T) { { name: "Invalid type", fields: fields{ - figs: make(map[string]*figFruit), - withered: make(map[string]witheredFig), - mutationsCh: make(chan Mutation, 1), - sources: make(map[string]SourceConfig), - sourceLocker: sync.RWMutex{}, + figs: make(map[string]*figFruit), + withered: make(map[string]witheredFig), + aliases: make(map[string]string), + mutationsCh: make(chan Mutation, 1), }, args: args{ flagVal: new(float32), // Unsupported type @@ -160,15 +155,14 @@ func TestTree_setValue(t *testing.T) { func TestTree_setValuesFromMap(t *testing.T) { tree := &figTree{ - figs: make(map[string]*figFruit), - withered: make(map[string]witheredFig), - sources: make(map[string]SourceConfig), - sourceLocker: sync.RWMutex{}, - mu: sync.RWMutex{}, - tracking: false, - mutationsCh: make(chan Mutation, 1), - flagSet: flag.NewFlagSet(os.Args[0], flag.ContinueOnError), - filterTests: true, + figs: make(map[string]*figFruit), + withered: make(map[string]witheredFig), + aliases: make(map[string]string), + mu: sync.RWMutex{}, + tracking: false, + mutationsCh: make(chan Mutation, 1), + flagSet: flag.NewFlagSet(os.Args[0], flag.ContinueOnError), + filterTests: true, } m := map[string]interface{}{ "name": "yahuah", diff --git a/mutations.go b/mutations.go index 9eeda67..c14d4b4 100644 --- a/mutations.go +++ b/mutations.go @@ -13,6 +13,9 @@ import ( func (tree *figTree) String(name string) *string { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() @@ -67,6 +70,9 @@ func (tree *figTree) String(name string) *string { func (tree *figTree) Bool(name string) *bool { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() @@ -113,6 +119,9 @@ func (tree *figTree) Bool(name string) *bool { func (tree *figTree) Int(name string) *int { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() @@ -160,6 +169,9 @@ func (tree *figTree) Int(name string) *int { func (tree *figTree) Int64(name string) *int64 { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() @@ -207,6 +219,9 @@ func (tree *figTree) Int64(name string) *int64 { func (tree *figTree) Float64(name string) *float64 { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() @@ -254,6 +269,9 @@ func (tree *figTree) Float64(name string) *float64 { func (tree *figTree) Duration(name string) *time.Duration { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() @@ -308,6 +326,9 @@ func (tree *figTree) Duration(name string) *time.Duration { func (tree *figTree) UnitDuration(name string) *time.Duration { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() @@ -362,6 +383,9 @@ func (tree *figTree) UnitDuration(name string) *time.Duration { func (tree *figTree) List(name string) *[]string { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() @@ -426,6 +450,9 @@ func (tree *figTree) List(name string) *[]string { func (tree *figTree) Map(name string) *map[string]string { tree.mu.RLock() defer tree.mu.RUnlock() + if _, exists := tree.aliases[name]; exists { + name = tree.aliases[name] + } fruit, ok := tree.figs[name] if !ok || fruit == nil { tree.mu.RUnlock() diff --git a/source.go b/source.go deleted file mode 100644 index 24f8f18..0000000 --- a/source.go +++ /dev/null @@ -1,42 +0,0 @@ -package figtree - -type SourceKind int - -const ( - SourceKindUnknown SourceKind = iota - SourceKindEnv - SourceKindFile - SourceKindFlag - SourceKindFlagEnv -) - -type SourceConfig interface { - Fetch() (string, error) - Kind() SourceKind -} - -func (tree *figTree) WithSource(source SourceConfig) error { - return nil -} - -func (tree *figTree) Source(name string) error { - tree.mu.RLock() - source, exists := tree.sources[name] - tree.mu.RUnlock() - if !exists { - return ErrSourceNotFound{} - } - result, err := source.Fetch() - if err != nil { - return err - } - tree.StoreString(name, result) - - return nil -} - -type ErrSourceNotFound struct{} - -func (e ErrSourceNotFound) Error() string { - return "source not found" -} diff --git a/types.go b/types.go index e5d2610..bbacf86 100644 --- a/types.go +++ b/types.go @@ -7,126 +7,133 @@ import ( "time" ) -// Plant defines the interface for configuration management. -type Plant interface { +type Withables interface { // WithCallback registers a new CallbackWhen with a CallbackFunc on a figFruit on the figTree by its name WithCallback(name string, whenCallback CallbackWhen, runThis CallbackFunc) Plant - - // SaveTo will store the Tree in a path file - SaveTo(path string) error - // ReadFrom will attempt to load the file into the Tree - ReadFrom(path string) error - + // WithAlias registers a short form of the name of a figFruit on the figTree + WithAlias(name, alias string) // WithRule attaches a RuleKind to a figFruit WithRule(name string, rule RuleKind) Plant - // WithTreeRule assigns a global rule on the Tree WithTreeRule(rule RuleKind) Plant - - // Fig returns a figFruit from the figTree by its name - Fig(name string) Flesh - - // Source runs the attached WithSource against the SourceConfig - Source(name string) error - // WithValidator binds a figValidatorFunc to a figFruit that returns Plant WithValidator(name string, validator func(interface{}) error) Plant +} + +type Savable interface { + // SaveTo will store the Tree in a path file + SaveTo(path string) error +} +type Readable interface { + // ReadFrom will attempt to load the file into the Tree + ReadFrom(path string) error +} + +type Parsable interface { // Parse can panic but interprets command line arguments defined with single dashes -example value -another sample Parse() error - // ParseFile can panic but also can throw an error because it will attempt to load either JSON, YAML or INI file passed into it ParseFile(filename string) error +} +type Mutable interface { + // Mutations receives Mutation data on a receiver channel + Mutations() <-chan Mutation // MutagenesisOfFig will look up a Fruit by name and return the Metagenesis of it MutagenesisOfFig(name string) Mutagenesis - // MutagenesisOf takes anything and returns the Mutagenesis of it MutagenesisOf(what interface{}) Mutagenesis +} - // ErrorFor returns an error attached to a named figFruit - ErrorFor(name string) error - - // Recall allows you to unlock the figTree from changes and resume tracking - Recall() - - // Curse allows you to lock the figTree from changes and stop tracking - Curse() - - // Mutations receives Mutation data on a receiver channel - Mutations() <-chan Mutation - - // Resurrect takes a nil figFruit in the figTree.figs map and reloads it from ENV or the config file if available - Resurrect(name string) - +type Loadable interface { // Load can panic but also can throw an error but will use the Environment Variable values if they are "EXAMPLE=value" or "ANOTHER=sample" Load() error - // LoadFile accepts a path to a JSON, YAML or INI file to set values LoadFile(path string) error - // Reload will refresh stored values of properties with their new Environment Variable values Reload() error +} - // Usage displays the helpful menu of figs registered using -h or -help - Usage() string +type Divine interface { + // Recall allows you to unlock the figTree from changes and resume tracking + Recall() + // Curse allows you to lock the figTree from changes and stop tracking + Curse() + // Resurrect takes a nil figFruit in the figTree.figs map and reloads it from ENV or the config file if available + Resurrect(name string) +} +type Intable interface { // Int returns a pointer to a registered int32 by name as -name=1 a pointer to 1 is returned Int(name string) *int // NewInt registers a new int32 flag by name and returns a pointer to the int32 storing the initial value NewInt(name string, value int, usage string) *int // StoreInt replaces name with value and can issue a Mutation when receiving on Mutations() StoreInt(name string, value int) Plant +} +type Intable64 interface { // Int64 returns a pointer to a registered int64 by name as -name=1 a pointer to 1 is returned Int64(name string) *int64 // NewInt64 registers a new int32 flag by name and returns a pointer to the int64 storing the initial value NewInt64(name string, value int64, usage string) *int64 // StoreInt64 replaces name with value and can issue a Mutation when receiving on Mutations() StoreInt64(name string, value int64) Plant +} +type Floatable interface { // Float64 returns a pointer to a registered float64 by name as -name=1.0 a pointer to 1.0 is returned Float64(name string) *float64 // NewFloat64 registers a new float64 flag by name and returns a pointer to the float64 storing the initial value NewFloat64(name string, value float64, usage string) *float64 // StoreFloat64 replaces name with value and can issue a Mutation when receiving on Mutations() StoreFloat64(name string, value float64) Plant +} +type String interface { // String returns a pointer to stored string by -name=value String(name string) *string // NewString registers a new string flag by name and returns a pointer to the string storing the initial value NewString(name, value, usage string) *string // StoreString replaces name with value and can issue a Mutation when receiving on Mutations() StoreString(name, value string) Plant +} +type Flaggable interface { // Bool returns a pointer to stored bool by -name=true Bool(name string) *bool // NewBool registers a new bool flag by name and returns a pointer to the bool storing the initial value NewBool(name string, value bool, usage string) *bool // StoreBool replaces name with value and can issue a Mutation when receiving on Mutations() StoreBool(name string, value bool) Plant +} +type Durable interface { // Duration returns a pointer to stored time.Duration (unitless) by name like -minutes=10 (requires multiplication of * time.Minute to match memetics of "minutes" flag name and human interpretation of this) Duration(name string) *time.Duration // NewDuration registers a new time.Duration by name and returns a pointer to it storing the initial value NewDuration(name string, value time.Duration, usage string) *time.Duration // StoreDuration replaces name with value and can issue a Mutation when receiving on Mutations() StoreDuration(name string, value time.Duration) Plant - // UnitDuration returns a pointer to stored time.Duration (-name=10 w/ units as time.Minute == 10 minutes time.Duration) UnitDuration(name string) *time.Duration // NewUnitDuration registers a new time.Duration by name and returns a pointer to it storing the initial value NewUnitDuration(name string, value, units time.Duration, usage string) *time.Duration // StoreUnitDuration replaces name with value and can issue a Mutation when receiving on Mutations() StoreUnitDuration(name string, value, units time.Duration) Plant +} +type Listable interface { // List returns a pointer to a []string containing strings List(name string) *[]string // NewList registers a new []string that can be assigned -name="ONE,TWO,THREE,FOUR" NewList(name string, value []string, usage string) *[]string // StoreList replaces name with value and can issue a Mutation when receiving on Mutations() StoreList(name string, value []string) Plant +} +type Mappable interface { // Map returns a pointer to a map[string]string containing strings Map(name string) *map[string]string // NewMap registers a new map[string]string that can be assigned -name="PROPERTY=VALUE,ANOTHER=VALUE" @@ -137,6 +144,45 @@ type Plant interface { StoreMap(name string, value map[string]string) Plant } +type CoreAbilities interface { + Withables + Savable + Readable + Parsable + Mutable + Loadable + Divine +} + +type CoreMutations interface { + Intable + Intable64 + Floatable + String + Flaggable + Durable + Listable + Mappable +} + +type Core interface { + // Fig returns a figFruit from the figTree by its name + Fig(name string) Flesh + + // ErrorFor returns an error attached to a named figFruit + ErrorFor(name string) error + + // Usage displays the helpful menu of figs registered using -h or -help + Usage() string +} + +// Plant defines the interface for configuration management. +type Plant interface { + Core + CoreAbilities + CoreMutations +} + // figTree stores figs that are defined by their name and figFruit as well as a mutations channel and tracking bool for Options.Tracking type figTree struct { ConfigFilePath string @@ -145,7 +191,7 @@ type figTree struct { pollinate bool figs map[string]*figFruit withered map[string]witheredFig - sources map[string]SourceConfig + aliases map[string]string sourceLocker sync.RWMutex mu sync.RWMutex tracking bool diff --git a/usage.go b/usage.go index e062f1c..ae9a78d 100644 --- a/usage.go +++ b/usage.go @@ -54,6 +54,15 @@ func (tree *figTree) Usage() string { } tree.mu.RUnlock() typeField := fmt.Sprintf("[%s]", typeStr) + var aliasList []string + for alias, name := range tree.aliases { + if name == f.Name { + aliasList = append(aliasList, alias) + } + } + if len(aliasList) > 0 { + flagStr = fmt.Sprintf("%s|-%s", strings.Join(aliasList, "|-"), flagStr) + } line := fmt.Sprintf(" -%-*s %-8s %s", maxFlagLen, flagStr, typeField, f.Usage)