From dcc73e9f014a3c44f27a86a6fec5d49777cf48a3 Mon Sep 17 00:00:00 2001 From: Andrei Merlescu Date: Sat, 26 Apr 2025 22:19:02 -0400 Subject: [PATCH 1/5] Added WithSource to the Plant --- go.mod | 2 + go.sum | 16 ++-- internal/sources/aes_string/config.go | 7 ++ .../sources/aws_secrets_manager/config.go | 47 ++++++++++ internal/sources/gpg_string/config.go | 7 ++ internal/sources/keeper_path/config.go | 7 ++ internal/sources/one_password_path/config.go | 7 ++ internal/sources/s3_aes_path/config.go | 7 ++ internal/sources/s3_gpg_path/config.go | 7 ++ internal/sources/s3_plaintext_path/config.go | 7 ++ internal/sources/vault_path/config.go | 7 ++ parsing.go | 4 + source.go | 86 +++++++++++++++++-- types.go | 20 +++-- 14 files changed, 211 insertions(+), 20 deletions(-) create mode 100644 internal/sources/aes_string/config.go create mode 100644 internal/sources/aws_secrets_manager/config.go create mode 100644 internal/sources/gpg_string/config.go create mode 100644 internal/sources/keeper_path/config.go create mode 100644 internal/sources/one_password_path/config.go create mode 100644 internal/sources/s3_aes_path/config.go create mode 100644 internal/sources/s3_gpg_path/config.go create mode 100644 internal/sources/s3_plaintext_path/config.go create mode 100644 internal/sources/vault_path/config.go diff --git a/go.mod b/go.mod index 4c2d193..fee2b29 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.23.4 require ( github.com/andreimerlescu/checkfs v1.0.3 + github.com/aws/aws-sdk-go v1.55.7 github.com/go-ini/ini v1.67.0 github.com/stretchr/testify v1.10.0 golang.org/x/term v0.31.0 @@ -12,6 +13,7 @@ require ( require ( github.com/davecgh/go-spew v1.1.1 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect golang.org/x/sys v0.32.0 // indirect ) diff --git a/go.sum b/go.sum index 0010e9f..d8b3526 100644 --- a/go.sum +++ b/go.sum @@ -1,24 +1,28 @@ -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/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= +github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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= 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.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/sources/aes_string/config.go b/internal/sources/aes_string/config.go new file mode 100644 index 0000000..551c09e --- /dev/null +++ b/internal/sources/aes_string/config.go @@ -0,0 +1,7 @@ +package aes_string + +type Config struct{} + +func (conf *Config) Fetch() (string, error) { + return "", nil +} diff --git a/internal/sources/aws_secrets_manager/config.go b/internal/sources/aws_secrets_manager/config.go new file mode 100644 index 0000000..b3a3e74 --- /dev/null +++ b/internal/sources/aws_secrets_manager/config.go @@ -0,0 +1,47 @@ +package aws_secrets_manager + +import ( + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/secretsmanager" +) + +func NewConfig() *Config { + return &Config{} +} + +type Config struct { + Region string + SecretName string + VersionID string +} + +func (conf *Config) Fetch() (string, error) { + sess, err := session.NewSession(&aws.Config{ + Region: aws.String(conf.Region), + }) + if err != nil { + return "", err + } + + svc := secretsmanager.New(sess) + + input := &secretsmanager.GetSecretValueInput{ + SecretId: aws.String(conf.SecretName), + } + + if conf.VersionID != "" { + input.VersionId = aws.String(conf.VersionID) + } + + result, err := svc.GetSecretValue(input) + if err != nil { + return "", err + } + + if result.SecretString != nil { + return *result.SecretString, nil + } + + return "", nil +} diff --git a/internal/sources/gpg_string/config.go b/internal/sources/gpg_string/config.go new file mode 100644 index 0000000..dbeb0ea --- /dev/null +++ b/internal/sources/gpg_string/config.go @@ -0,0 +1,7 @@ +package gpg_string + +type Config struct{} + +func (conf *Config) Fetch() (string, error) { + return "", nil +} diff --git a/internal/sources/keeper_path/config.go b/internal/sources/keeper_path/config.go new file mode 100644 index 0000000..a8712ac --- /dev/null +++ b/internal/sources/keeper_path/config.go @@ -0,0 +1,7 @@ +package keeper_path + +type Config struct{} + +func (conf *Config) Fetch() (string, error) { + return "", nil +} diff --git a/internal/sources/one_password_path/config.go b/internal/sources/one_password_path/config.go new file mode 100644 index 0000000..9a86738 --- /dev/null +++ b/internal/sources/one_password_path/config.go @@ -0,0 +1,7 @@ +package one_password_path + +type Config struct{} + +func (conf *Config) Fetch() (string, error) { + return "", nil +} diff --git a/internal/sources/s3_aes_path/config.go b/internal/sources/s3_aes_path/config.go new file mode 100644 index 0000000..c3b1b81 --- /dev/null +++ b/internal/sources/s3_aes_path/config.go @@ -0,0 +1,7 @@ +package s3_aes_path + +type Config struct{} + +func (conf *Config) Fetch() (string, error) { + return "", nil +} diff --git a/internal/sources/s3_gpg_path/config.go b/internal/sources/s3_gpg_path/config.go new file mode 100644 index 0000000..62a4e72 --- /dev/null +++ b/internal/sources/s3_gpg_path/config.go @@ -0,0 +1,7 @@ +package s3_gpg_path + +type Config struct{} + +func (conf *Config) Fetch() (string, error) { + return "", nil +} diff --git a/internal/sources/s3_plaintext_path/config.go b/internal/sources/s3_plaintext_path/config.go new file mode 100644 index 0000000..5893ea6 --- /dev/null +++ b/internal/sources/s3_plaintext_path/config.go @@ -0,0 +1,7 @@ +package s3_plaintext_path + +type Config struct{} + +func (conf *Config) Fetch() (string, error) { + return "", nil +} diff --git a/internal/sources/vault_path/config.go b/internal/sources/vault_path/config.go new file mode 100644 index 0000000..311cb5c --- /dev/null +++ b/internal/sources/vault_path/config.go @@ -0,0 +1,7 @@ +package vault_path + +type Config struct{} + +func (conf *Config) Fetch() (string, error) { + return "", nil +} diff --git a/parsing.go b/parsing.go index ba299b4..48d7ae6 100644 --- a/parsing.go +++ b/parsing.go @@ -22,6 +22,10 @@ func (tree *figTree) Parse() (err error) { } } tree.readEnv() + err = tree.fetchFromSources() + if err != nil { + return err + } return tree.validateAll() } diff --git a/source.go b/source.go index 24f8f18..06ec645 100644 --- a/source.go +++ b/source.go @@ -1,25 +1,97 @@ package figtree +import ( + "fmt" +) + type SourceKind int const ( - SourceKindUnknown SourceKind = iota - SourceKindEnv - SourceKindFile - SourceKindFlag - SourceKindFlagEnv + SourceAWSSecretsManager SourceKind = iota + SourceAESString + SourceGPGString + SourceOnePasswordPath + SourceVaultPath + SourceKeeperPath + SourceS3PlaintextPath + SourceS3AESPath + SourceS3GPGPath ) type SourceConfig interface { Fetch() (string, error) - Kind() SourceKind } -func (tree *figTree) WithSource(source SourceConfig) error { +func (s SourceKind) String() string { + switch s { + case SourceAWSSecretsManager: + return "aws_secrets_manager" + case SourceAESString: + return "aes_string" + case SourceGPGString: + return "gpg_string" + case SourceOnePasswordPath: + return "one_password_path" + case SourceVaultPath: + return "vault_path" + case SourceKeeperPath: + return "keeper_path" + case SourceS3PlaintextPath: + return "s3_plaintext_path" + case SourceS3AESPath: + return "s3_aes_path" + case SourceS3GPGPath: + return "s3_gpg_path" + default: + return "unknown" + } +} + +// LoadAllFromSource will attempt to load all the sources into the Tree +func (tree *figTree) LoadAllFromSource() error { + return tree.fetchFromSources() +} + +// fetchFromSources retrieves values from configured sources +func (tree *figTree) fetchFromSources() error { + tree.sourceLocker.Lock() + tree.mu.RLock() + defer tree.sourceLocker.Unlock() + defer tree.mu.RUnlock() + if tree.sources == nil { + return nil + } + errs := make([]error, 0) + for propName, sourceConfig := range tree.sources { + value, err := sourceConfig.Fetch() + if err != nil { + errs = append(errs, fmt.Errorf("failed to fetch from source for %s: %w", propName, err)) + continue + } + + fruit, exists := tree.figs[propName] + if !exists || fruit == nil { + continue + } + + tree.Store(fruit.Mutagenesis, fruit.name, value) + } + + return nil +} + +func (tree *figTree) WithSource(name string, source SourceConfig) Plant { + tree.sourceLocker.Lock() + tree.mu.Lock() + defer tree.sourceLocker.Unlock() + defer tree.mu.Unlock() + tree.sources[name] = source return nil } func (tree *figTree) Source(name string) error { + tree.sourceLocker.Lock() + defer tree.sourceLocker.Unlock() tree.mu.RLock() source, exists := tree.sources[name] tree.mu.RUnlock() diff --git a/types.go b/types.go index e5d2610..5a129fd 100644 --- a/types.go +++ b/types.go @@ -12,25 +12,31 @@ type Plant 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 - // 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 + // WithValidator binds a figValidatorFunc to a figFruit that returns Plant + WithValidator(name string, validator func(interface{}) error) Plant + + // WithSource attaches a SourceKind to a figFruit that returns Plant + WithSource(name string, config SourceConfig) 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 + // 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 + // LoadAllFromSource will attempt to load all the sources into the Tree + LoadAllFromSource() error // Parse can panic but interprets command line arguments defined with single dashes -example value -another sample Parse() error From 5a22af84f5b0a3f1e7ac412b99c5a2cbbdca26b0 Mon Sep 17 00:00:00 2001 From: Andrei Merlescu Date: Sat, 26 Apr 2025 23:01:15 -0400 Subject: [PATCH 2/5] Bumped version for feature/source to 2.1.0 --- VERSION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VERSION b/VERSION index 923fd4d..1defe53 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.0.8 +v2.1.0 From cec23551eb5a42517f5e9af5453a0293cba17216 Mon Sep 17 00:00:00 2001 From: Andrei Merlescu Date: Sun, 15 Jun 2025 05:40:14 -0400 Subject: [PATCH 3/5] Added WithAlias --- alias.go | 10 +++++++ alias_test.go | 48 +++++++++++++++++++++++++++++++++ figs.go => figtree.go | 1 + figs_test.go => figtree_test.go | 24 ----------------- mutations.go | 27 +++++++++++++++++++ source.go | 3 +-- types.go | 13 +++++---- 7 files changed, 95 insertions(+), 31 deletions(-) create mode 100644 alias.go create mode 100644 alias_test.go rename figs.go => figtree.go (98%) rename figs_test.go => figtree_test.go (95%) 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..1ba1787 --- /dev/null +++ b/alias_test.go @@ -0,0 +1,48 @@ +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()) + + assert.Equal(t, valueLong, *figs.String(cmdLong)) + assert.Equal(t, valueLong, *figs.String(cmdAliasLong)) + 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()) + // 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 + + }) +} diff --git a/figs.go b/figtree.go similarity index 98% rename from figs.go rename to figtree.go index 6e2be35..795bfe1 100644 --- a/figs.go +++ b/figtree.go @@ -72,6 +72,7 @@ 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), diff --git a/figs_test.go b/figtree_test.go similarity index 95% rename from figs_test.go rename to figtree_test.go index e626089..7a2b4c5 100644 --- a/figs_test.go +++ b/figtree_test.go @@ -262,30 +262,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/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 index 06ec645..4802648 100644 --- a/source.go +++ b/source.go @@ -20,6 +20,7 @@ const ( type SourceConfig interface { Fetch() (string, error) + Kind() SourceKind } func (s SourceKind) String() string { @@ -90,8 +91,6 @@ func (tree *figTree) WithSource(name string, source SourceConfig) Plant { } func (tree *figTree) Source(name string) error { - tree.sourceLocker.Lock() - defer tree.sourceLocker.Unlock() tree.mu.RLock() source, exists := tree.sources[name] tree.mu.RUnlock() diff --git a/types.go b/types.go index 5a129fd..ca80fb5 100644 --- a/types.go +++ b/types.go @@ -12,6 +12,14 @@ type Plant 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 + // WithAlias registers a short form of the name of a figFruit on the figTree + WithAlias(name, alias string) + + // 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 + // WithRule attaches a RuleKind to a figFruit WithRule(name string, rule RuleKind) Plant @@ -24,11 +32,6 @@ type Plant interface { // WithSource attaches a SourceKind to a figFruit that returns Plant WithSource(name string, config SourceConfig) 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 - // Fig returns a figFruit from the figTree by its name Fig(name string) Flesh From ffefe36ab3fe733e9f0d445749c8a4831c0d2925 Mon Sep 17 00:00:00 2001 From: Andrei Merlescu Date: Sun, 15 Jun 2025 07:33:49 -0400 Subject: [PATCH 4/5] Added WithAlias --- VERSION | 2 +- alias_test.go | 43 ++++++ figtree.go | 2 - figtree_test.go | 13 +- go.mod | 8 +- go.sum | 22 +-- internal/sources/aes_string/config.go | 7 - .../sources/aws_secrets_manager/config.go | 47 ------- internal/sources/gpg_string/config.go | 7 - internal/sources/keeper_path/config.go | 7 - internal/sources/one_password_path/config.go | 7 - internal/sources/s3_aes_path/config.go | 7 - internal/sources/s3_gpg_path/config.go | 7 - internal/sources/s3_plaintext_path/config.go | 7 - internal/sources/vault_path/config.go | 7 - internals_test.go | 66 +++++---- parsing.go | 4 - source.go | 113 ---------------- types.go | 125 ++++++++++++------ usage.go | 5 + 20 files changed, 179 insertions(+), 327 deletions(-) delete mode 100644 internal/sources/aes_string/config.go delete mode 100644 internal/sources/aws_secrets_manager/config.go delete mode 100644 internal/sources/gpg_string/config.go delete mode 100644 internal/sources/keeper_path/config.go delete mode 100644 internal/sources/one_password_path/config.go delete mode 100644 internal/sources/s3_aes_path/config.go delete mode 100644 internal/sources/s3_gpg_path/config.go delete mode 100644 internal/sources/s3_plaintext_path/config.go delete mode 100644 internal/sources/vault_path/config.go delete mode 100644 source.go diff --git a/VERSION b/VERSION index 1defe53..77989f5 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -v2.1.0 +v2.0.9 \ No newline at end of file diff --git a/alias_test.go b/alias_test.go index 1ba1787..85e970a 100644 --- a/alias_test.go +++ b/alias_test.go @@ -15,12 +15,33 @@ func TestWithAlias(t *testing.T) { 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}) @@ -35,6 +56,7 @@ func TestWithAlias(t *testing.T) { 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)) @@ -45,4 +67,25 @@ func TestWithAlias(t *testing.T) { 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/figtree.go b/figtree.go index 795bfe1..489873c 100644 --- a/figtree.go +++ b/figtree.go @@ -75,8 +75,6 @@ func With(opts Options) Plant { 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/figtree_test.go b/figtree_test.go index 7a2b4c5..eecfcff 100644 --- a/figtree_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) { diff --git a/go.mod b/go.mod index fee2b29..3d10f4c 100644 --- a/go.mod +++ b/go.mod @@ -3,17 +3,15 @@ module github.com/andreimerlescu/figtree/v2 go 1.23.4 require ( - github.com/andreimerlescu/checkfs v1.0.3 - github.com/aws/aws-sdk-go v1.55.7 + 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/jmespath/go-jmespath v0.4.0 // 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 d8b3526..9fcc3df 100644 --- a/go.sum +++ b/go.sum @@ -1,28 +1,18 @@ -github.com/andreimerlescu/checkfs v1.0.3 h1:vnYAPI+Yu+4YfuvY8Jjnq26p7WhIvw4XjCJ5w3S0sjI= -github.com/andreimerlescu/checkfs v1.0.3/go.mod h1:ADaqjiRJf3gmyENLS3v9bJIaEH00IOeM48cXxVwy1JY= -github.com/aws/aws-sdk-go v1.55.7 h1:UJrkFq7es5CShfBwlWAC8DA077vp8PyVbQd3lqLiztE= -github.com/aws/aws-sdk-go v1.55.7/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +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= github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= -github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= -github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= -github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= -github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= 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/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 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.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= -golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -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.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/internal/sources/aes_string/config.go b/internal/sources/aes_string/config.go deleted file mode 100644 index 551c09e..0000000 --- a/internal/sources/aes_string/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package aes_string - -type Config struct{} - -func (conf *Config) Fetch() (string, error) { - return "", nil -} diff --git a/internal/sources/aws_secrets_manager/config.go b/internal/sources/aws_secrets_manager/config.go deleted file mode 100644 index b3a3e74..0000000 --- a/internal/sources/aws_secrets_manager/config.go +++ /dev/null @@ -1,47 +0,0 @@ -package aws_secrets_manager - -import ( - "github.com/aws/aws-sdk-go/aws" - "github.com/aws/aws-sdk-go/aws/session" - "github.com/aws/aws-sdk-go/service/secretsmanager" -) - -func NewConfig() *Config { - return &Config{} -} - -type Config struct { - Region string - SecretName string - VersionID string -} - -func (conf *Config) Fetch() (string, error) { - sess, err := session.NewSession(&aws.Config{ - Region: aws.String(conf.Region), - }) - if err != nil { - return "", err - } - - svc := secretsmanager.New(sess) - - input := &secretsmanager.GetSecretValueInput{ - SecretId: aws.String(conf.SecretName), - } - - if conf.VersionID != "" { - input.VersionId = aws.String(conf.VersionID) - } - - result, err := svc.GetSecretValue(input) - if err != nil { - return "", err - } - - if result.SecretString != nil { - return *result.SecretString, nil - } - - return "", nil -} diff --git a/internal/sources/gpg_string/config.go b/internal/sources/gpg_string/config.go deleted file mode 100644 index dbeb0ea..0000000 --- a/internal/sources/gpg_string/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package gpg_string - -type Config struct{} - -func (conf *Config) Fetch() (string, error) { - return "", nil -} diff --git a/internal/sources/keeper_path/config.go b/internal/sources/keeper_path/config.go deleted file mode 100644 index a8712ac..0000000 --- a/internal/sources/keeper_path/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package keeper_path - -type Config struct{} - -func (conf *Config) Fetch() (string, error) { - return "", nil -} diff --git a/internal/sources/one_password_path/config.go b/internal/sources/one_password_path/config.go deleted file mode 100644 index 9a86738..0000000 --- a/internal/sources/one_password_path/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package one_password_path - -type Config struct{} - -func (conf *Config) Fetch() (string, error) { - return "", nil -} diff --git a/internal/sources/s3_aes_path/config.go b/internal/sources/s3_aes_path/config.go deleted file mode 100644 index c3b1b81..0000000 --- a/internal/sources/s3_aes_path/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package s3_aes_path - -type Config struct{} - -func (conf *Config) Fetch() (string, error) { - return "", nil -} diff --git a/internal/sources/s3_gpg_path/config.go b/internal/sources/s3_gpg_path/config.go deleted file mode 100644 index 62a4e72..0000000 --- a/internal/sources/s3_gpg_path/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package s3_gpg_path - -type Config struct{} - -func (conf *Config) Fetch() (string, error) { - return "", nil -} diff --git a/internal/sources/s3_plaintext_path/config.go b/internal/sources/s3_plaintext_path/config.go deleted file mode 100644 index 5893ea6..0000000 --- a/internal/sources/s3_plaintext_path/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package s3_plaintext_path - -type Config struct{} - -func (conf *Config) Fetch() (string, error) { - return "", nil -} diff --git a/internal/sources/vault_path/config.go b/internal/sources/vault_path/config.go deleted file mode 100644 index 311cb5c..0000000 --- a/internal/sources/vault_path/config.go +++ /dev/null @@ -1,7 +0,0 @@ -package vault_path - -type Config struct{} - -func (conf *Config) Fetch() (string, error) { - return "", nil -} 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/parsing.go b/parsing.go index 48d7ae6..ba299b4 100644 --- a/parsing.go +++ b/parsing.go @@ -22,10 +22,6 @@ func (tree *figTree) Parse() (err error) { } } tree.readEnv() - err = tree.fetchFromSources() - if err != nil { - return err - } return tree.validateAll() } diff --git a/source.go b/source.go deleted file mode 100644 index 4802648..0000000 --- a/source.go +++ /dev/null @@ -1,113 +0,0 @@ -package figtree - -import ( - "fmt" -) - -type SourceKind int - -const ( - SourceAWSSecretsManager SourceKind = iota - SourceAESString - SourceGPGString - SourceOnePasswordPath - SourceVaultPath - SourceKeeperPath - SourceS3PlaintextPath - SourceS3AESPath - SourceS3GPGPath -) - -type SourceConfig interface { - Fetch() (string, error) - Kind() SourceKind -} - -func (s SourceKind) String() string { - switch s { - case SourceAWSSecretsManager: - return "aws_secrets_manager" - case SourceAESString: - return "aes_string" - case SourceGPGString: - return "gpg_string" - case SourceOnePasswordPath: - return "one_password_path" - case SourceVaultPath: - return "vault_path" - case SourceKeeperPath: - return "keeper_path" - case SourceS3PlaintextPath: - return "s3_plaintext_path" - case SourceS3AESPath: - return "s3_aes_path" - case SourceS3GPGPath: - return "s3_gpg_path" - default: - return "unknown" - } -} - -// LoadAllFromSource will attempt to load all the sources into the Tree -func (tree *figTree) LoadAllFromSource() error { - return tree.fetchFromSources() -} - -// fetchFromSources retrieves values from configured sources -func (tree *figTree) fetchFromSources() error { - tree.sourceLocker.Lock() - tree.mu.RLock() - defer tree.sourceLocker.Unlock() - defer tree.mu.RUnlock() - if tree.sources == nil { - return nil - } - errs := make([]error, 0) - for propName, sourceConfig := range tree.sources { - value, err := sourceConfig.Fetch() - if err != nil { - errs = append(errs, fmt.Errorf("failed to fetch from source for %s: %w", propName, err)) - continue - } - - fruit, exists := tree.figs[propName] - if !exists || fruit == nil { - continue - } - - tree.Store(fruit.Mutagenesis, fruit.name, value) - } - - return nil -} - -func (tree *figTree) WithSource(name string, source SourceConfig) Plant { - tree.sourceLocker.Lock() - tree.mu.Lock() - defer tree.sourceLocker.Unlock() - defer tree.mu.Unlock() - tree.sources[name] = source - 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 ca80fb5..bbacf86 100644 --- a/types.go +++ b/types.go @@ -7,135 +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 - // WithAlias registers a short form of the name of a figFruit on the figTree WithAlias(name, alias string) - - // 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 - // 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 - // WithValidator binds a figValidatorFunc to a figFruit that returns Plant WithValidator(name string, validator func(interface{}) error) Plant +} - // WithSource attaches a SourceKind to a figFruit that returns Plant - WithSource(name string, config SourceConfig) 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 +type Savable interface { + // SaveTo will store the Tree in a path file + SaveTo(path string) error +} - // LoadAllFromSource will attempt to load all the sources into the Tree - LoadAllFromSource() 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" @@ -146,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 @@ -154,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..9aaed07 100644 --- a/usage.go +++ b/usage.go @@ -54,6 +54,11 @@ func (tree *figTree) Usage() string { } tree.mu.RUnlock() typeField := fmt.Sprintf("[%s]", typeStr) + for alias, name := range tree.aliases { + if name == f.Name { + flagStr = fmt.Sprintf("%s|-%s", alias, flagStr) + } + } line := fmt.Sprintf(" -%-*s %-8s %s", maxFlagLen, flagStr, typeField, f.Usage) From 4a5481a6fbe9c8b824b7485b72c36a4ff601fd81 Mon Sep 17 00:00:00 2001 From: Andrei Merlescu Date: Sun, 15 Jun 2025 07:41:03 -0400 Subject: [PATCH 5/5] Adjusted usage --- usage.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/usage.go b/usage.go index 9aaed07..ae9a78d 100644 --- a/usage.go +++ b/usage.go @@ -54,11 +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 { - flagStr = fmt.Sprintf("%s|-%s", alias, flagStr) + 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)