diff --git a/pkg/flags/README.md b/pkg/flags/README.md new file mode 100644 index 00000000..94002671 --- /dev/null +++ b/pkg/flags/README.md @@ -0,0 +1,77 @@ +# Flags Package + +## Overview + +The `pkg/flags` package provides utilities for registering, handling, and transferring command-line flags. It serves as the bridge between the command-line interface and the configuration options. + +## File Structure + +``` +pkg/flags/ +├── README.md # This documentation file +├── registry.go # Core interface and generic implementation +├── registry_mock.go # Mock implementation for testing +├── registry_diff.go # Diff-specific registry +├── registry_apply.go # Apply-specific registry +├── registry_sync.go # Sync-specific registry +├── registry_template.go # Template-specific registry +├── registry_lint.go # Lint-specific registry +├── registry_destroy.go # Destroy-specific registry +├── registry_fetch.go # Fetch-specific registry +├── registry_list.go # List-specific registry +├── registry_status.go # Status-specific registry +├── registry_test.go # Tests for registry implementation +├── flag_handler.go # FlagHandler interface +├── flag_handler_mock.go # Mock implementation of FlagHandler +├── flag_handler_test.go # Tests for flag handler implementations +├── flag_value.go # Generic flag value retrieval functions +└── flag_value_test.go # Tests for flag value functions +``` + +## Components + +- **FlagHandler Interface**: Defines how components handle flag values with boolean return for success +- **FlagRegistry**: Manages flag registration and transfer +- **Command-specific Registries**: Specialized registries for each command +- **Helper Functions**: Utilities for getting flag values of different types + +## Key Features + +- **Type Safety**: Helper functions for safely retrieving typed flag values +- **Flag Registration**: Centralized registration of flags to command objects +- **Flag Transfer**: Mechanism to transfer flag values to option objects +- **Flag Existence Checking**: Methods to check if flags are registered or handled +- **Success Indication**: Boolean return values to indicate if flags were successfully handled + +## Usage + +```go +// Create a registry +registry := flags.NewGenericFlagRegistry() + +// Register flags to a command +registry.RegisterFlags(cmd) + +// Transfer flags to options +registry.TransferFlags(cmd, opts) + +// Check if a flag is registered +if registry.IsFlagRegistered("my-flag") { + // Do something +} + +// Handle a flag with success indication +handled := opts.HandleFlag("flag-name", value, changed) +if !handled { + // Flag wasn't recognized +} +``` + +## Testing + +The package includes mock implementations for testing: + +- `MockFlagHandler`: For testing flag handling logic +- `MockFlagRegistry`: For testing flag registration and transfer + +These mocks can be used to verify flag handling behavior without needing real command objects. diff --git a/pkg/flags/common.go b/pkg/flags/common.go deleted file mode 100644 index 09aa5af7..00000000 --- a/pkg/flags/common.go +++ /dev/null @@ -1,49 +0,0 @@ -package flags - -import ( - "github.com/spf13/cobra" -) - -// FlagRegistrar defines an interface for registering and transferring flags -type FlagRegistrar interface { - RegisterFlags(cmd *cobra.Command) - TransferFlags(cmd *cobra.Command, opts interface{}) -} - -// FlagHandler is a generic interface for handling flag values -type FlagHandler interface { - // HandleFlag receives a flag name, value, and whether it was changed - HandleFlag(name string, value interface{}, changed bool) -} - -// GenericFlagRegistrar is a base struct for flag registrars -type GenericFlagRegistrar struct { - // Map of flag names to their values - values map[string]interface{} -} - -// NewGenericFlagRegistrar creates a new GenericFlagRegistrar -func NewGenericFlagRegistrar() *GenericFlagRegistrar { - return &GenericFlagRegistrar{ - values: make(map[string]interface{}), - } -} - -// TransferFlags transfers all registered flags to the options -func (r *GenericFlagRegistrar) TransferFlags(cmd *cobra.Command, opts interface{}) { - if handler, ok := opts.(FlagHandler); ok { - flags := cmd.Flags() - - // Transfer each registered flag - for name, value := range r.values { - changed := flags.Changed(name) - handler.HandleFlag(name, value, changed) - } - } -} - -// RegisterBoolFlag registers a boolean flag and stores its reference -func (r *GenericFlagRegistrar) RegisterBoolFlag(cmd *cobra.Command, name string, value *bool, defaultValue bool, usage string) { - cmd.Flags().BoolVar(value, name, defaultValue, usage) - r.values[name] = value -} diff --git a/pkg/flags/common_test.go b/pkg/flags/common_test.go deleted file mode 100644 index b81056b8..00000000 --- a/pkg/flags/common_test.go +++ /dev/null @@ -1,140 +0,0 @@ -package flags - -import ( - "testing" - - "github.com/spf13/cobra" - "github.com/stretchr/testify/assert" -) - -// MockFlagHandler implements FlagHandler for testing -type MockFlagHandler struct { - handledFlags map[string]interface{} - changedFlags map[string]bool -} - -func NewMockFlagHandler() *MockFlagHandler { - return &MockFlagHandler{ - handledFlags: make(map[string]interface{}), - changedFlags: make(map[string]bool), - } -} - -func (h *MockFlagHandler) HandleFlag(name string, value interface{}, changed bool) { - h.handledFlags[name] = value - h.changedFlags[name] = changed -} - -func TestNewGenericFlagRegistrar(t *testing.T) { - registrar := NewGenericFlagRegistrar() - assert.NotNil(t, registrar) - assert.NotNil(t, registrar.values) - assert.Len(t, registrar.values, 0) -} - -func TestGenericFlagRegistrar_RegisterBoolFlag(t *testing.T) { - registrar := NewGenericFlagRegistrar() - cmd := &cobra.Command{Use: "test"} - - var testFlag bool - registrar.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag") - - // Verify the flag was registered - flag := cmd.Flags().Lookup("test-flag") - assert.NotNil(t, flag) - assert.Equal(t, "test-flag", flag.Name) - assert.Equal(t, "false", flag.DefValue) - assert.Equal(t, "Test flag", flag.Usage) - - // Verify the value was stored in the registrar - value, exists := registrar.values["test-flag"] - assert.True(t, exists) - assert.Equal(t, &testFlag, value) -} - -func TestGenericFlagRegistrar_TransferFlags_NoChanges(t *testing.T) { - registrar := NewGenericFlagRegistrar() - cmd := &cobra.Command{Use: "test"} - - var testFlag bool - registrar.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag") - - // Create a mock handler - handler := NewMockFlagHandler() - - // Transfer flags (none changed) - registrar.TransferFlags(cmd, handler) - - // Verify the handler was called with the right parameters - assert.Equal(t, &testFlag, handler.handledFlags["test-flag"]) - assert.False(t, handler.changedFlags["test-flag"]) -} - -func TestGenericFlagRegistrar_TransferFlags_WithChanges(t *testing.T) { - registrar := NewGenericFlagRegistrar() - cmd := &cobra.Command{Use: "test"} - - var testFlag bool - registrar.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag") - - // Simulate flag being set on command line - err := cmd.Flags().Set("test-flag", "true") - assert.NoError(t, err) - testFlag = true // Value would be updated by cobra - - // Create a mock handler - handler := NewMockFlagHandler() - - // Transfer flags (with changes) - registrar.TransferFlags(cmd, handler) - - // Verify the handler was called with the right parameters - assert.Equal(t, &testFlag, handler.handledFlags["test-flag"]) - assert.True(t, handler.changedFlags["test-flag"]) - assert.True(t, *handler.handledFlags["test-flag"].(*bool)) -} - -func TestGenericFlagRegistrar_TransferFlags_NonHandler(t *testing.T) { - registrar := NewGenericFlagRegistrar() - cmd := &cobra.Command{Use: "test"} - - var testFlag bool - registrar.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag") - - // Use a non-handler type - nonHandler := struct{}{} - - // This should not panic - registrar.TransferFlags(cmd, nonHandler) -} - -func TestGenericFlagRegistrar_MultipleFlags(t *testing.T) { - registrar := NewGenericFlagRegistrar() - cmd := &cobra.Command{Use: "test"} - - var boolFlag bool - var boolFlag2 bool - - registrar.RegisterBoolFlag(cmd, "bool-flag", &boolFlag, false, "Boolean flag") - registrar.RegisterBoolFlag(cmd, "bool-flag2", &boolFlag2, true, "Another boolean flag") - - // Set one flag - err := cmd.Flags().Set("bool-flag", "true") - assert.NoError(t, err) - boolFlag = true // Value would be updated by cobra - - // Create a mock handler - handler := NewMockFlagHandler() - - // Transfer flags - registrar.TransferFlags(cmd, handler) - - // Verify both flags were handled correctly - assert.Equal(t, &boolFlag, handler.handledFlags["bool-flag"]) - assert.True(t, handler.changedFlags["bool-flag"]) - assert.True(t, *handler.handledFlags["bool-flag"].(*bool)) - - assert.Equal(t, &boolFlag2, handler.handledFlags["bool-flag2"]) - assert.False(t, handler.changedFlags["bool-flag2"]) - assert.True(t, *handler.handledFlags["bool-flag2"].(*bool)) // Default is true -} diff --git a/pkg/flags/diff.go b/pkg/flags/diff.go deleted file mode 100644 index 23211194..00000000 --- a/pkg/flags/diff.go +++ /dev/null @@ -1,22 +0,0 @@ -package flags - -import "github.com/spf13/cobra" - -// DiffFlagRegistrar handles flags specific to the diff command -type DiffFlagRegistrar struct { - *GenericFlagRegistrar - IncludeCRDs bool -} - -// NewDiffFlagRegistrar creates a new DiffFlagRegistrar -func NewDiffFlagRegistrar() *DiffFlagRegistrar { - return &DiffFlagRegistrar{ - GenericFlagRegistrar: NewGenericFlagRegistrar(), - } -} - -// RegisterFlags registers diff-specific flags -func (r *DiffFlagRegistrar) RegisterFlags(cmd *cobra.Command) { - r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing") - // Diff doesn't have skip-crds -} diff --git a/pkg/flags/flag_handler.go b/pkg/flags/flag_handler.go new file mode 100644 index 00000000..03f79904 --- /dev/null +++ b/pkg/flags/flag_handler.go @@ -0,0 +1,8 @@ +package flags + +// FlagHandler is a generic interface for handling flag values +type FlagHandler interface { + // HandleFlag receives a flag name, value, and whether it was changed + // Returns true if the flag was handled, false otherwise + HandleFlag(name string, value interface{}, changed bool) bool +} diff --git a/pkg/flags/flag_handler_mock.go b/pkg/flags/flag_handler_mock.go new file mode 100644 index 00000000..4426a132 --- /dev/null +++ b/pkg/flags/flag_handler_mock.go @@ -0,0 +1,20 @@ +package flags + +// MockFlagHandler implements FlagHandler for testing +type MockFlagHandler struct { + handledFlags map[string]interface{} + changedFlags map[string]bool +} + +func NewMockFlagHandler() *MockFlagHandler { + return &MockFlagHandler{ + handledFlags: make(map[string]interface{}), + changedFlags: make(map[string]bool), + } +} + +func (h *MockFlagHandler) HandleFlag(name string, value interface{}, changed bool) bool { + h.handledFlags[name] = value + h.changedFlags[name] = changed + return true +} diff --git a/pkg/flags/flag_handler_test.go b/pkg/flags/flag_handler_test.go new file mode 100644 index 00000000..02584727 --- /dev/null +++ b/pkg/flags/flag_handler_test.go @@ -0,0 +1,51 @@ +package flags + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestMockFlagHandler_HandleFlag_ReturnValue(t *testing.T) { + // Create a mock handler + mockHandler := NewMockFlagHandler() + + // Test that it returns true for any flag + result := mockHandler.HandleFlag("test-flag", true, false) + assert.True(t, result, "MockHandler should return true for any flag") + + // Test with multiple flags + result = mockHandler.HandleFlag("another-flag", "value", true) + assert.True(t, result, "MockHandler should return true for any flag") + + // Verify the flags were stored correctly + assert.Equal(t, true, mockHandler.handledFlags["test-flag"]) + assert.Equal(t, "value", mockHandler.handledFlags["another-flag"]) + assert.Equal(t, false, mockHandler.changedFlags["test-flag"]) + assert.Equal(t, true, mockHandler.changedFlags["another-flag"]) +} + +// CustomFlagHandler implements FlagHandler for testing specific return values +type CustomFlagHandler struct{} + +func (h *CustomFlagHandler) HandleFlag(name string, value interface{}, changed bool) bool { + // Only handle specific flags + switch name { + case "known-flag": + return true + default: + return false + } +} + +func TestCustomFlagHandler_HandleFlag_ReturnValue(t *testing.T) { + handler := &CustomFlagHandler{} + + // Test with a recognized flag + result := handler.HandleFlag("known-flag", true, false) + assert.True(t, result, "Should return true for recognized flag") + + // Test with an unrecognized flag + result = handler.HandleFlag("unknown-flag", true, false) + assert.False(t, result, "Should return false for unrecognized flag") +} diff --git a/pkg/flags/flag_value.go b/pkg/flags/flag_value.go new file mode 100644 index 00000000..16a19623 --- /dev/null +++ b/pkg/flags/flag_value.go @@ -0,0 +1,43 @@ +package flags + +// GetFlagValue is a generic function to get flag values with type safety +func GetFlagValue[T any](registry FlagRegistry, name string) (T, bool) { + var zero T + values := registry.GetValues() + if value, exists := values[name]; exists { + if typedValue, ok := value.(*T); ok { + return *typedValue, true + } + } + return zero, false +} + +// GetBoolFlagValue is a convenience function to get a boolean flag value +func GetBoolFlagValue(registry FlagRegistry, name string) (bool, bool) { + return GetFlagValue[bool](registry, name) +} + +// GetStringFlagValue is a convenience function to get a string flag value +func GetStringFlagValue(registry FlagRegistry, name string) (string, bool) { + return GetFlagValue[string](registry, name) +} + +// GetStringSliceFlagValue is a convenience function to get a string slice flag value +func GetStringSliceFlagValue(registry FlagRegistry, name string) ([]string, bool) { + return GetFlagValue[[]string](registry, name) +} + +// GetIntFlagValue is a convenience function to get an integer flag value +func GetIntFlagValue(registry FlagRegistry, name string) (int, bool) { + return GetFlagValue[int](registry, name) +} + +// GetInt64FlagValue is a convenience function to get an int64 flag value +func GetInt64FlagValue(registry FlagRegistry, name string) (int64, bool) { + return GetFlagValue[int64](registry, name) +} + +// GetFloat64FlagValue is a convenience function to get a float64 flag value +func GetFloat64FlagValue(registry FlagRegistry, name string) (float64, bool) { + return GetFlagValue[float64](registry, name) +} diff --git a/pkg/flags/flags_value_test.go b/pkg/flags/flags_value_test.go new file mode 100644 index 00000000..1b849d1d --- /dev/null +++ b/pkg/flags/flags_value_test.go @@ -0,0 +1,220 @@ +package flags + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestGetFlagValue(t *testing.T) { + registry := NewMockFlagRegistry() + + // Register some test flags + boolValue := false + registry.values["bool-flag"] = &boolValue + + stringValue := "test" + registry.values["string-flag"] = &stringValue + + stringSliceValue := []string{"one", "two", "three"} + registry.values["string-slice-flag"] = &stringSliceValue + + intValue := 42 + registry.values["int-flag"] = &intValue + + // Test getting boolean flag + gotBool, exists := GetFlagValue[bool](registry, "bool-flag") + assert.True(t, exists) + assert.Equal(t, false, gotBool) + + // Test getting string flag + gotString, exists := GetFlagValue[string](registry, "string-flag") + assert.True(t, exists) + assert.Equal(t, "test", gotString) + + // Test getting string slice flag + gotStringSlice, exists := GetFlagValue[[]string](registry, "string-slice-flag") + assert.True(t, exists) + assert.Equal(t, []string{"one", "two", "three"}, gotStringSlice) + + // Test getting int flag + gotInt, exists := GetFlagValue[int](registry, "int-flag") + assert.True(t, exists) + assert.Equal(t, 42, gotInt) + + // Test getting non-existent flag + _, exists = GetFlagValue[bool](registry, "non-existent") + assert.False(t, exists) + + // Test getting flag with wrong type + _, exists = GetFlagValue[string](registry, "bool-flag") + assert.False(t, exists) +} + +func TestGetBoolFlagValue(t *testing.T) { + registry := NewMockFlagRegistry() + + // Register a boolean flag + boolValue := false + registry.values["bool-flag"] = &boolValue + + // Test getting the flag value + value, exists := GetBoolFlagValue(registry, "bool-flag") + assert.True(t, exists) + assert.False(t, value) + + // Change the value + *registry.values["bool-flag"].(*bool) = true + + // Test getting the updated value + value, exists = GetBoolFlagValue(registry, "bool-flag") + assert.True(t, exists) + assert.True(t, value) + + // Test getting a non-existent flag + value, exists = GetBoolFlagValue(registry, "non-existent") + assert.False(t, exists) + assert.False(t, value) // Default value for bool + + // Test getting a flag with wrong type + stringValue := "test" + registry.values["string-flag"] = &stringValue + value, exists = GetBoolFlagValue(registry, "string-flag") + assert.False(t, exists) + assert.False(t, value) // Default value for bool +} + +func TestGetStringFlagValue(t *testing.T) { + registry := NewMockFlagRegistry() + + // Register a string flag + stringValue := "test" + registry.values["string-flag"] = &stringValue + + // Test getting the flag value + value, exists := GetStringFlagValue(registry, "string-flag") + assert.True(t, exists) + assert.Equal(t, "test", value) + + // Change the value + *registry.values["string-flag"].(*string) = "updated" + + // Test getting the updated value + value, exists = GetStringFlagValue(registry, "string-flag") + assert.True(t, exists) + assert.Equal(t, "updated", value) + + // Test getting a non-existent flag + value, exists = GetStringFlagValue(registry, "non-existent") + assert.False(t, exists) + assert.Equal(t, "", value) // Default value for string + + // Test getting a flag with wrong type + boolValue := true + registry.values["bool-flag"] = &boolValue + value, exists = GetStringFlagValue(registry, "bool-flag") + assert.False(t, exists) + assert.Equal(t, "", value) // Default value for string +} + +func TestGetStringSliceFlagValue(t *testing.T) { + registry := NewMockFlagRegistry() + + // Register a string slice flag + sliceValue := []string{"one", "two", "three"} + registry.values["slice-flag"] = &sliceValue + + // Test getting the flag value + value, exists := GetStringSliceFlagValue(registry, "slice-flag") + assert.True(t, exists) + assert.Equal(t, []string{"one", "two", "three"}, value) + + // Change the value + *registry.values["slice-flag"].(*[]string) = []string{"updated"} + + // Test getting the updated value + value, exists = GetStringSliceFlagValue(registry, "slice-flag") + assert.True(t, exists) + assert.Equal(t, []string{"updated"}, value) + + // Test getting a non-existent flag + value, exists = GetStringSliceFlagValue(registry, "non-existent") + assert.False(t, exists) + assert.Nil(t, value) // Default value for slice + + // Test getting a flag with wrong type + boolValue := true + registry.values["bool-flag"] = &boolValue + value, exists = GetStringSliceFlagValue(registry, "bool-flag") + assert.False(t, exists) + assert.Nil(t, value) // Default value for slice +} + +func TestGetIntFlagValue(t *testing.T) { + registry := NewMockFlagRegistry() + + // Register an int flag + intValue := 42 + registry.values["int-flag"] = &intValue + + // Test getting the flag value + value, exists := GetIntFlagValue(registry, "int-flag") + assert.True(t, exists) + assert.Equal(t, 42, value) + + // Change the value + *registry.values["int-flag"].(*int) = 100 + + // Test getting the updated value + value, exists = GetIntFlagValue(registry, "int-flag") + assert.True(t, exists) + assert.Equal(t, 100, value) + + // Test getting a non-existent flag + value, exists = GetIntFlagValue(registry, "non-existent") + assert.False(t, exists) + assert.Equal(t, 0, value) // Default value for int + + // Test getting a flag with wrong type + boolValue := true + registry.values["bool-flag"] = &boolValue + value, exists = GetIntFlagValue(registry, "bool-flag") + assert.False(t, exists) + assert.Equal(t, 0, value) // Default value for int +} + +func TestGetInt64FlagValue(t *testing.T) { + registry := NewMockFlagRegistry() + + // Register an int64 flag + int64Value := int64(42) + registry.values["int64-flag"] = &int64Value + + // Test getting the flag value + value, exists := GetInt64FlagValue(registry, "int64-flag") + assert.True(t, exists) + assert.Equal(t, int64(42), value) + + // Test getting a non-existent flag + value, exists = GetInt64FlagValue(registry, "non-existent") + assert.False(t, exists) + assert.Equal(t, int64(0), value) // Default value for int64 +} + +func TestGetFloat64FlagValue(t *testing.T) { + registry := NewMockFlagRegistry() + + // Register a float64 flag + float64Value := 3.14 + registry.values["float64-flag"] = &float64Value + + // Test getting the flag value + value, exists := GetFloat64FlagValue(registry, "float64-flag") + assert.True(t, exists) + assert.Equal(t, 3.14, value) + + // Test getting a non-existent flag + value, exists = GetFloat64FlagValue(registry, "non-existent") + assert.False(t, exists) + assert.Equal(t, 0.0, value) // Default value for float64 +} diff --git a/pkg/flags/registry.go b/pkg/flags/registry.go new file mode 100644 index 00000000..669e245b --- /dev/null +++ b/pkg/flags/registry.go @@ -0,0 +1,64 @@ +package flags + +import "github.com/spf13/cobra" + +// FlagRegistry defines an interface for registering and transferring flags +type FlagRegistry interface { + RegisterFlags(cmd *cobra.Command) + TransferFlags(cmd *cobra.Command, opts interface{}) + GetRegisteredFlagNames() []string + GetValues() map[string]interface{} + IsFlagRegistered(name string) bool +} + +// GenericFlagRegistry is a base struct for flag registries +type GenericFlagRegistry struct { + // Map of flag names to their values + values map[string]interface{} +} + +// NewGenericFlagRegistry creates a new GenericFlagRegistrar +func NewGenericFlagRegistry() *GenericFlagRegistry { + return &GenericFlagRegistry{ + values: make(map[string]interface{}), + } +} + +// GetValues returns the internal values map +func (r *GenericFlagRegistry) GetValues() map[string]interface{} { + return r.values +} + +// GetRegisteredFlagNames returns the names of all registered flags +func (r *GenericFlagRegistry) GetRegisteredFlagNames() []string { + names := make([]string, 0, len(r.values)) + for name := range r.values { + names = append(names, name) + } + return names +} + +// IsFlagRegistered checks if a flag is registered in the registry +func (r *GenericFlagRegistry) IsFlagRegistered(name string) bool { + _, exists := r.values[name] + return exists +} + +// TransferFlags transfers all registered flags to the options +func (r *GenericFlagRegistry) TransferFlags(cmd *cobra.Command, opts interface{}) { + if handler, ok := opts.(FlagHandler); ok { + flags := cmd.Flags() + + // Transfer each registered flag + for name, value := range r.values { + changed := flags.Changed(name) + handler.HandleFlag(name, value, changed) + } + } +} + +// RegisterBoolFlag registers a boolean flag and stores its reference +func (r *GenericFlagRegistry) RegisterBoolFlag(cmd *cobra.Command, name string, value *bool, defaultValue bool, usage string) { + cmd.Flags().BoolVar(value, name, defaultValue, usage) + r.values[name] = value +} diff --git a/pkg/flags/apply.go b/pkg/flags/registry_apply.go similarity index 51% rename from pkg/flags/apply.go rename to pkg/flags/registry_apply.go index d34b69a8..bf1e2b7c 100644 --- a/pkg/flags/apply.go +++ b/pkg/flags/registry_apply.go @@ -2,22 +2,22 @@ package flags import "github.com/spf13/cobra" -// ApplyFlagRegistrar handles flags specific to the apply command -type ApplyFlagRegistrar struct { - *GenericFlagRegistrar +// ApplyFlagRegistry handles flags specific to the apply command +type ApplyFlagRegistry struct { + *GenericFlagRegistry IncludeCRDs bool SkipCRDs bool } -// NewApplyFlagRegistrar creates a new ApplyFlagRegistrar -func NewApplyFlagRegistrar() *ApplyFlagRegistrar { - return &ApplyFlagRegistrar{ - GenericFlagRegistrar: NewGenericFlagRegistrar(), +// NewApplyFlagRegistry creates a new ApplyFlagRegistry +func NewApplyFlagRegistry() *ApplyFlagRegistry { + return &ApplyFlagRegistry{ + GenericFlagRegistry: NewGenericFlagRegistry(), } } // RegisterFlags registers apply-specific flags -func (r *ApplyFlagRegistrar) RegisterFlags(cmd *cobra.Command) { +func (r *ApplyFlagRegistry) RegisterFlags(cmd *cobra.Command) { r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing") r.RegisterBoolFlag(cmd, "skip-crds", &r.SkipCRDs, false, "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present") } diff --git a/pkg/flags/registry_diff.go b/pkg/flags/registry_diff.go new file mode 100644 index 00000000..bd7edec4 --- /dev/null +++ b/pkg/flags/registry_diff.go @@ -0,0 +1,22 @@ +package flags + +import "github.com/spf13/cobra" + +// DiffFlagRegistry handles flags specific to the diff command +type DiffFlagRegistry struct { + *GenericFlagRegistry + IncludeCRDs bool +} + +// NewDiffFlagRegistry creates a new DiffFlagRegistry +func NewDiffFlagRegistry() *DiffFlagRegistry { + return &DiffFlagRegistry{ + GenericFlagRegistry: NewGenericFlagRegistry(), + } +} + +// RegisterFlags registers diff-specific flags +func (r *DiffFlagRegistry) RegisterFlags(cmd *cobra.Command) { + r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing") + // Diff doesn't have skip-crds +} diff --git a/pkg/flags/registry_diff_test.go b/pkg/flags/registry_diff_test.go new file mode 100644 index 00000000..7da4a185 --- /dev/null +++ b/pkg/flags/registry_diff_test.go @@ -0,0 +1,27 @@ +package flags + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +func TestDiffFlagRegisty(t *testing.T) { + registry := NewDiffFlagRegistry() + + // Create a test command to register flags + cmd := &cobra.Command{Use: "test"} + registry.RegisterFlags(cmd) + + // Get the names of registered flags + registeredFlags := registry.GetRegisteredFlagNames() + + // Verify that include-crds and skip-crds are registered + assert.Contains(t, registeredFlags, "include-crds") + + // Get and verify the default values using the generic function + includeCRDs, exists := GetFlagValue[bool](registry, "include-crds") + assert.True(t, exists) + assert.False(t, includeCRDs) +} diff --git a/pkg/flags/registry_mock.go b/pkg/flags/registry_mock.go new file mode 100644 index 00000000..b6ede6e3 --- /dev/null +++ b/pkg/flags/registry_mock.go @@ -0,0 +1,24 @@ +package flags + +import "github.com/spf13/cobra" + +// MockFlagRegistry implements FlagRegistrar for testing +type MockFlagRegistry struct { + *GenericFlagRegistry +} + +func NewMockFlagRegistry() *MockFlagRegistry { + return &MockFlagRegistry{ + GenericFlagRegistry: NewGenericFlagRegistry(), + } +} + +// RegisterFlags implements the FlagRegistrar interface for testing +func (r *MockFlagRegistry) RegisterFlags(cmd *cobra.Command) { + // Mock implementation does nothing +} + +// GetValues returns the internal values map +func (r *MockFlagRegistry) GetValues() map[string]interface{} { + return r.values +} diff --git a/pkg/flags/sync.go b/pkg/flags/registry_sync.go similarity index 51% rename from pkg/flags/sync.go rename to pkg/flags/registry_sync.go index a5498759..4b025f89 100644 --- a/pkg/flags/sync.go +++ b/pkg/flags/registry_sync.go @@ -2,22 +2,22 @@ package flags import "github.com/spf13/cobra" -// SyncFlagRegistrar handles flags specific to the sync command -type SyncFlagRegistrar struct { - *GenericFlagRegistrar +// SyncFlagRegistry handles flags specific to the sync command +type SyncFlagRegistry struct { + *GenericFlagRegistry IncludeCRDs bool SkipCRDs bool } -// NewSyncFlagRegistrar creates a new SyncFlagRegistrar -func NewSyncFlagRegistrar() *SyncFlagRegistrar { - return &SyncFlagRegistrar{ - GenericFlagRegistrar: NewGenericFlagRegistrar(), +// NewSyncFlagRegistry creates a new SyncFlagRegistry +func NewSyncFlagRegistry() *SyncFlagRegistry { + return &SyncFlagRegistry{ + GenericFlagRegistry: NewGenericFlagRegistry(), } } // RegisterFlags registers sync-specific flags -func (r *SyncFlagRegistrar) RegisterFlags(cmd *cobra.Command) { +func (r *SyncFlagRegistry) RegisterFlags(cmd *cobra.Command) { r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing") r.RegisterBoolFlag(cmd, "skip-crds", &r.SkipCRDs, false, "if set, no CRDs will be installed on sync. By default, CRDs are installed if not already present") } diff --git a/pkg/flags/registry_template.go b/pkg/flags/registry_template.go new file mode 100644 index 00000000..7314a252 --- /dev/null +++ b/pkg/flags/registry_template.go @@ -0,0 +1,22 @@ +package flags + +import "github.com/spf13/cobra" + +// TemplateFlagRegistry handles flags specific to the template command +type TemplateFlagRegistry struct { + *GenericFlagRegistry + IncludeCRDs bool +} + +// NewTemplateFlagRegistry creates a new TemplateFlagRegistry +func NewTemplateFlagRegistry() *TemplateFlagRegistry { + return &TemplateFlagRegistry{ + GenericFlagRegistry: NewGenericFlagRegistry(), + } +} + +// RegisterFlags registers template-specific flags +func (r *TemplateFlagRegistry) RegisterFlags(cmd *cobra.Command) { + r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing") + // Template doesn't have skip-crds +} diff --git a/pkg/flags/registry_test.go b/pkg/flags/registry_test.go new file mode 100644 index 00000000..a023d2d8 --- /dev/null +++ b/pkg/flags/registry_test.go @@ -0,0 +1,176 @@ +package flags + +import ( + "testing" + + "github.com/spf13/cobra" + "github.com/stretchr/testify/assert" +) + +func TestNewGenericFlagRegistrar(t *testing.T) { + registry := NewMockFlagRegistry() + assert.NotNil(t, registry) + assert.NotNil(t, registry.values) + assert.Len(t, registry.values, 0) +} + +func TestGenericFlagRegistry_RegisterBoolFlag(t *testing.T) { + registry := NewMockFlagRegistry() + cmd := &cobra.Command{Use: "test"} + + var testFlag bool + registry.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag") + + // Verify the flag was registered + flag := cmd.Flags().Lookup("test-flag") + assert.NotNil(t, flag) + assert.Equal(t, "test-flag", flag.Name) + assert.Equal(t, "false", flag.DefValue) + assert.Equal(t, "Test flag", flag.Usage) + + // Verify the value was stored in the registry + value, exists := registry.values["test-flag"] + assert.True(t, exists) + assert.Equal(t, &testFlag, value) + + // Test the generic GetFlagValue function + flagValue, exists := GetFlagValue[bool](registry, "test-flag") + assert.True(t, exists) + assert.Equal(t, false, flagValue) +} + +func TestGenericFlagRegistry_TransferFlags_NoChanges(t *testing.T) { + registry := NewMockFlagRegistry() + cmd := &cobra.Command{Use: "test"} + + var testFlag bool + registry.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag") + + // Create a mock handler + handler := NewMockFlagHandler() + + // Transfer flags (none changed) + registry.TransferFlags(cmd, handler) + + // Verify the handler was called with the right parameters + assert.Equal(t, &testFlag, handler.handledFlags["test-flag"]) + assert.False(t, handler.changedFlags["test-flag"]) +} + +func TestGenericFlagRegistry_TransferFlags_WithChanges(t *testing.T) { + registry := NewMockFlagRegistry() + cmd := &cobra.Command{Use: "test"} + + var testFlag bool + registry.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag") + + // Simulate flag being set on command line + err := cmd.Flags().Set("test-flag", "true") + assert.NoError(t, err) + testFlag = true // Value would be updated by cobra + + // Create a mock handler + handler := NewMockFlagHandler() + + // Transfer flags (with changes) + registry.TransferFlags(cmd, handler) + + // Verify the handler was called with the right parameters + assert.Equal(t, &testFlag, handler.handledFlags["test-flag"]) + assert.True(t, handler.changedFlags["test-flag"]) + assert.True(t, *handler.handledFlags["test-flag"].(*bool)) +} + +func TestGenericFlagRegistry_TransferFlags_NonHandler(t *testing.T) { + registry := NewMockFlagRegistry() + cmd := &cobra.Command{Use: "test"} + + var testFlag bool + registry.RegisterBoolFlag(cmd, "test-flag", &testFlag, false, "Test flag") + + // Use a non-handler type + nonHandler := struct{}{} + + // This should not panic + registry.TransferFlags(cmd, nonHandler) +} + +func TestGenericFlagRegistry_MultipleFlags(t *testing.T) { + registry := NewMockFlagRegistry() + cmd := &cobra.Command{Use: "test"} + + var boolFlag bool + var boolFlag2 bool + + registry.RegisterBoolFlag(cmd, "bool-flag", &boolFlag, false, "Boolean flag") + registry.RegisterBoolFlag(cmd, "bool-flag2", &boolFlag2, true, "Another boolean flag") + + // Set one flag + err := cmd.Flags().Set("bool-flag", "true") + assert.NoError(t, err) + boolFlag = true // Value would be updated by cobra + + // Create a mock handler + handler := NewMockFlagHandler() + + // Transfer flags + registry.TransferFlags(cmd, handler) + + // Verify both flags were handled correctly + assert.Equal(t, &boolFlag, handler.handledFlags["bool-flag"]) + assert.True(t, handler.changedFlags["bool-flag"]) + assert.True(t, *handler.handledFlags["bool-flag"].(*bool)) + + assert.Equal(t, &boolFlag2, handler.handledFlags["bool-flag2"]) + assert.False(t, handler.changedFlags["bool-flag2"]) + assert.True(t, *handler.handledFlags["bool-flag2"].(*bool)) // Default is true +} + +func TestGenericFlagRegistry_GetRegisteredFlagNames(t *testing.T) { + registry := NewMockFlagRegistry() + + // Register some test flags + boolValue := false + registry.values["bool-flag"] = &boolValue + + stringValue := "test" + registry.values["string-flag"] = &stringValue + + stringSliceValue := []string{"one", "two", "three"} + registry.values["string-slice-flag"] = &stringSliceValue + + // Test GetRegisteredFlagNames + names := registry.GetRegisteredFlagNames() + assert.Len(t, names, 3) + assert.Contains(t, names, "bool-flag") + assert.Contains(t, names, "string-flag") + assert.Contains(t, names, "string-slice-flag") +} + +func TestGenericFlagRegistry_IsFlagRegistered(t *testing.T) { + registry := NewGenericFlagRegistry() + + // Initially, no flags are registered + assert.False(t, registry.IsFlagRegistered("test-flag")) + + // Register a flag + boolValue := false + registry.values["test-flag"] = &boolValue + + // Now the flag should be registered + assert.True(t, registry.IsFlagRegistered("test-flag")) + + // Check a non-existent flag + assert.False(t, registry.IsFlagRegistered("non-existent")) + + // Register another flag + stringValue := "test" + registry.values["string-flag"] = &stringValue + + // Both flags should be registered + assert.True(t, registry.IsFlagRegistered("test-flag")) + assert.True(t, registry.IsFlagRegistered("string-flag")) + + // Case sensitivity check + assert.False(t, registry.IsFlagRegistered("TEST-FLAG")) +} diff --git a/pkg/flags/template.go b/pkg/flags/template.go deleted file mode 100644 index 22966728..00000000 --- a/pkg/flags/template.go +++ /dev/null @@ -1,21 +0,0 @@ -package flags - -import "github.com/spf13/cobra" - -// TemplateFlagRegistrar handles flags specific to the template command -type TemplateFlagRegistrar struct { - *GenericFlagRegistrar - IncludeCRDs bool -} - -// NewTemplateFlagRegistrar creates a new TemplateFlagRegistrar -func NewTemplateFlagRegistrar() *TemplateFlagRegistrar { - return &TemplateFlagRegistrar{ - GenericFlagRegistrar: NewGenericFlagRegistrar(), - } -} - -// RegisterFlags registers template-specific flags -func (r *TemplateFlagRegistrar) RegisterFlags(cmd *cobra.Command) { - r.RegisterBoolFlag(cmd, "include-crds", &r.IncludeCRDs, false, "include CRDs in the diffing") -}