-
Notifications
You must be signed in to change notification settings - Fork 4
/
Copy pathfromstruct.go
115 lines (95 loc) · 2.77 KB
/
fromstruct.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
package multistate
import (
"context"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"github.com/go-qbit/multistate/expr"
)
type Implementation interface{}
type Action struct {
Caption string
From expr.Expression
Set States
Reset States
OnDo ActionDoFunc
Availabler Availabler
}
type Cluster struct {
Caption string
Expr expr.Expression
}
func NewFromStruct(s Implementation) *Multistate {
mst := New("New")
rtS := reflect.TypeOf(s)
rvS := reflect.ValueOf(s)
rvStruct := rvS
rtStruct := rtS
if rvStruct.Kind() == reflect.Ptr {
rvStruct = rvStruct.Elem()
rtStruct = rtStruct.Elem()
}
for i := 0; i < rvStruct.NumField(); i++ {
ft := rtStruct.Field(i)
if ft.Type.String() != "multistate.State" {
continue
}
strBit, exists := ft.Tag.Lookup("bit")
if !exists {
panic(fmt.Sprintf("Missed required tag 'bit' for field '%s'", ft.Name))
}
bit, err := strconv.ParseUint(strBit, 10, 7)
if err != nil {
panic(fmt.Sprintf("Invalid 'bit' value for field '%s'", ft.Name))
}
caption := ft.Name
if t, exists := ft.Tag.Lookup("caption"); exists {
caption = t
}
rvStruct.Field(i).Set(reflect.ValueOf(mst.MustAddState(uint8(bit), camelCaseToSnake(ft.Name), caption)))
}
for i := 0; i < rtS.NumMethod(); i++ {
mt := rtS.Method(i)
if strings.HasPrefix(mt.Name, "Action") {
values := rvS.Method(i).Call(nil)
if len(values) != 1 || values[0].Type().String() != "multistate.Action" {
panic(fmt.Sprintf("The action method %s must return the multistate.Action structure", mt.Name))
}
action := values[0].Interface().(Action)
caption := mt.Name[6:]
if action.Caption != "" {
caption = action.Caption
}
if action.From == nil {
action.From = expr.Empty()
}
mst.MustAddAction(camelCaseToSnake(mt.Name[6:]), caption, action.From, action.Set, action.Reset, action.OnDo, action.Availabler)
} else if mt.Name == "OnDoAction" {
cb, ok := rvS.Method(i).Interface().(func(context.Context, Entity, uint64, uint64, string, ...interface{}) error)
if !ok {
panic(fmt.Sprintf("OnDoAction must fit OnDoCallback type "))
}
mst.SetOnDoCallback(cb)
} else if mt.Name == "Clusters" {
values := rvS.Method(i).Call(nil)
clusters, ok := values[0].Interface().([]Cluster)
if !ok {
panic(fmt.Sprintf("The Clusters method must return the []multistate.Cluster type"))
}
for _, c := range clusters {
mst.AddCluster(c.Caption, c.Expr)
}
}
}
mst.MustCompile()
return mst
}
var matchFirstCap = regexp.MustCompile("(.)([A-Z][a-z]+)")
var matchAllCap = regexp.MustCompile("([a-z0-9])([A-Z])")
func camelCaseToSnake(s string) string {
snake := matchFirstCap.ReplaceAllString(s, "${1}_${2}")
snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}")
return strings.ToLower(snake)
}