Skip to content

Commit

Permalink
feat: v2 version, refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
dapeng committed Dec 17, 2024
1 parent 671e8ef commit 70d848d
Show file tree
Hide file tree
Showing 226 changed files with 5,805 additions and 17,377 deletions.
552 changes: 0 additions & 552 deletions cemetery.go

This file was deleted.

765 changes: 0 additions & 765 deletions cemetery_test.go

This file was deleted.

72 changes: 72 additions & 0 deletions coffin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
package gone

import (
"reflect"
"sort"
)

// coffin represents a component container in the gone framework
type coffin struct {
name string
goner any

order int
onlyForName bool
forceReplace bool

defaultTypeMap map[reflect.Type]bool
lazyFill bool
needInitBeforeUse bool
isFill bool
isInit bool
}

func newCoffin(goner any) *coffin {
_, needInitBeforeUse := goner.(Initiator)
if !needInitBeforeUse {
_, needInitBeforeUse = goner.(InitiatorNoError)
}
if !needInitBeforeUse {
_, needInitBeforeUse = goner.(NamedProvider)
}

return &coffin{
goner: goner,
defaultTypeMap: make(map[reflect.Type]bool),
needInitBeforeUse: needInitBeforeUse,
}
}

func (c *coffin) isDefault(t reflect.Type) bool {
return c.defaultTypeMap[t]
}

// coffinList is a slice of coffin pointers that implements sort.Interface
type coffinList []*coffin

func (c coffinList) Len() int {
return len(c)
}

func (c coffinList) Less(i, j int) bool {
return c[i].order < c[j].order
}

func (c coffinList) Swap(i, j int) {
c[i], c[j] = c[j], c[i]
}

// SortCoffins sorts a slice of coffins by their order
func SortCoffins(coffins []*coffin) {
sort.Sort(coffinList(coffins))
}

func isInitiator(co *coffin) bool {
if _, ok := co.goner.(Initiator); ok {
return true
}
if _, ok := co.goner.(InitiatorNoError); ok {
return true
}
return false
}
148 changes: 148 additions & 0 deletions coffin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package gone

import (
"reflect"
"testing"
)

func TestCoffinListSort(t *testing.T) {
tests := []struct {
name string
coffins []*coffin
expected []*coffin
}{
{
name: "Sort by order - ascending",
coffins: []*coffin{
{name: "C", order: 3},
{name: "A", order: 1},
{name: "B", order: 2},
},
expected: []*coffin{
{name: "A", order: 1},
{name: "B", order: 2},
{name: "C", order: 3},
},
},
{
name: "Sort with same orders",
coffins: []*coffin{
{name: "A", order: 1},
{name: "B", order: 1},
{name: "C", order: 1},
},
expected: []*coffin{
{name: "A", order: 1},
{name: "B", order: 1},
{name: "C", order: 1},
},
},
{
name: "Sort with negative orders",
coffins: []*coffin{
{name: "C", order: 1},
{name: "A", order: -2},
{name: "B", order: -1},
},
expected: []*coffin{
{name: "A", order: -2},
{name: "B", order: -1},
{name: "C", order: 1},
},
},
{
name: "Empty slice",
coffins: []*coffin{},
expected: []*coffin{},
},
{
name: "Single element",
coffins: []*coffin{
{name: "A", order: 1},
},
expected: []*coffin{
{name: "A", order: 1},
},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
// Test Sort method
SortCoffins(tt.coffins)

// Check if the result matches expected
if !reflect.DeepEqual(tt.coffins, tt.expected) {
t.Errorf("SortCoffins() got = %v, want %v", formatCoffins(tt.coffins), formatCoffins(tt.expected))
}

// Verify that the result is actually sorted
for i := 1; i < len(tt.coffins); i++ {
if tt.coffins[i-1].order > tt.coffins[i].order {
t.Errorf("SortCoffins() result is not sorted at index %d: %v", i, formatCoffins(tt.coffins))
}
}
})
}
}

func TestCoffinListInterface(t *testing.T) {
list := coffinList{
{name: "B", order: 2},
{name: "A", order: 1},
{name: "C", order: 3},
}

// Test Len()
if got := list.Len(); got != 3 {
t.Errorf("coffinList.Len() = %v, want %v", got, 3)
}

// Test Less()
tests := []struct {
i, j int
expected bool
}{
{0, 1, false}, // 2 > 1, should be false
{1, 2, true}, // 1 < 3, should be true
{0, 2, true}, // 2 < 3, should be true
}

for _, tt := range tests {
t.Run(formatLessTest(list[tt.i], list[tt.j]), func(t *testing.T) {
if got := list.Less(tt.i, tt.j); got != tt.expected {
t.Errorf("coffinList.Less(%v, %v) = %v, want %v",
tt.i, tt.j, got, tt.expected)
}
})
}

// Test Swap()
original := make([]*coffin, len(list))
copy(original, list)

list.Swap(0, 2)
if list[0].order != original[2].order || list[2].order != original[0].order {
t.Errorf("coffinList.Swap(0, 2) failed, got %v, want swapped %v",
formatCoffins(list), formatCoffins(original))
}
}

// Helper function to format coffins for error messages
func formatCoffins(coffins []*coffin) string {
result := "["
for i, c := range coffins {
if i > 0 {
result += ", "
}
result += "{" + c.name + ":" + string(rune('0'+c.order)) + "}"
}
result += "]"
return result
}

// Helper function to format Less test description
func formatLessTest(a, b *coffin) string {
return "Less " + a.name + "(" + string(rune('0'+a.order)) + ") " +
b.name + "(" + string(rune('0'+b.order)) + ")"
}
160 changes: 160 additions & 0 deletions config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package gone

import (
"github.com/gone-io/gone/internal/json"
"os"
"reflect"
"strconv"
"time"
)

const ConfigureName = "configure"

// Configure defines the interface for configuration providers
// Get retrieves a configuration value by key, storing it in v, with a default value if not found
type Configure interface {
Get(key string, v any, defaultVal string) error
}

// ConfigProvider implements a provider for injecting configuration values
// It uses an underlying Configure implementation to retrieve values
type ConfigProvider struct {
Flag
configure Configure `gone:"configure"` // The Configure implementation to use
}

// Name returns the provider name "config" used for registration
func (s *ConfigProvider) Name() string {
return "config"
}

func (s *ConfigProvider) Init() {}

// Provide implements the provider interface to inject configuration values
// Parameters:
// - tagConf: The tag configuration string containing key and default value
// - t: The reflect.Type of the value to provide
//
// Returns:
// - The configured value of type t
// - Error if configuration fails
func (s *ConfigProvider) Provide(tagConf string, t reflect.Type) (any, error) {
// Parse the tag string into a map and ordered keys
m, keys := TagStringParse(tagConf)
if len(keys) == 0 {
return nil, NewInnerError("config-key is empty", ConfigError)
}

// Get the first key and its default value
key := keys[0]
defaultValue := m[key]
if defaultValue == "" {
defaultValue = m["default"] // Fallback to "default" key if no value
}

// Create new value of requested type and configure it
value := reflect.New(t)
err := s.configure.Get(key, value.Interface(), defaultValue)
if err != nil {
return nil, ToError(err)
}
return value.Elem().Interface(), nil
}

type EnvConfigure struct {
Flag
}

// Get retrieves a configuration value from environment variables with fallback to default value.
// Supports type conversion for various Go types including string, int, float, bool, and structs.
//
// Parameters:
// - key: Environment variable name to look up
// - v: Pointer to variable where the value will be stored
// - defaultVal: Default value if environment variable is not set
//
// Returns error if:
// - v is not a pointer
// - Type conversion fails
// - Unsupported type is provided
func (s *EnvConfigure) Get(key string, v any, defaultVal string) error {
// Get environment variable value, fallback to default if not set
key = convertUppercaseCamel("GONE_" + key)
env := os.Getenv(key)
if env == "" {
env = defaultVal
}

// Verify v is a pointer
rv := reflect.ValueOf(v)
if rv.Kind() != reflect.Ptr {
return NewInnerError("Value must be a pointer", ConfigError)
}

// Type switch to handle different pointer types
switch ptr := v.(type) {
case *string:
// String type needs no conversion
*ptr = env
case *int:
// Convert string to int
val, err := strconv.Atoi(env)
if err != nil {
return ToError(err)
}
*ptr = val
case *int64:
// Convert string to int64
val, err := strconv.ParseInt(env, 10, 64)
if err != nil {
return ToError(err)
}
*ptr = val
case *float64:
// Convert string to float64
val, err := strconv.ParseFloat(env, 64)
if err != nil {
return ToError(err)
}
*ptr = val
case *bool:
// Convert string to bool
val, err := strconv.ParseBool(env)
if err != nil {
return ToError(err)
}
*ptr = val
case *uint:
// Convert string to uint
val, err := strconv.ParseUint(env, 10, 64)
if err != nil {
return ToError(err)
}
*ptr = uint(val)
case *uint64:
// Convert string to uint64
val, err := strconv.ParseUint(env, 10, 64)
if err != nil {
return ToError(err)
}
*ptr = val
case *time.Duration:
// Convert string to time.Duration
val, err := time.ParseDuration(env)
if err != nil {
return ToError(err)
}
*ptr = val
default:
// Handle struct types by JSON unmarshal
if rv.Elem().Kind() == reflect.Struct {
err := json.Unmarshal([]byte(env), v)
if err != nil {
return ToError(err)
}
return nil
}
return NewInnerError("Unsupported type by EnvConfigure", ConfigError)
}
return nil
}
Loading

0 comments on commit 70d848d

Please sign in to comment.