package machine
import (
"crypto/rand"
"encoding/hex"
"errors"
"fmt"
"maps"
"os"
"reflect"
"slices"
"strconv"
"strings"
)
func DiffStates (states1 S , states2 S ) S {
return slicesFilter (states1 , func (name string , i int ) bool {
return !slices .Contains (states2 , name )
})
}
func SameStates (states1 S , states2 S ) S {
return slicesFilter (states1 , func (name string , i int ) bool {
return slices .Contains (states2 , name )
})
}
func StatesEqual (states1 S , states2 S ) bool {
return slicesEvery (states1 , states2 ) && slicesEvery (states2 , states1 )
}
func CloneSchema (stateStruct Schema ) Schema {
ret := make (Schema )
for name , state := range stateStruct {
ret [name ] = cloneState (state )
}
return ret
}
func cloneState(state State ) State {
stateCopy := State {
Auto : state .Auto ,
Multi : state .Multi ,
}
if state .Require != nil {
stateCopy .Require = slices .Clone (state .Require )
}
if state .Add != nil {
stateCopy .Add = slices .Clone (state .Add )
}
if state .Remove != nil {
stateCopy .Remove = slices .Clone (state .Remove )
}
if state .After != nil {
stateCopy .After = slices .Clone (state .After )
}
if state .Tags != nil {
stateCopy .Tags = slices .Clone (state .Tags )
}
return stateCopy
}
func IsActiveTick (tick uint64 ) bool {
return tick %2 == 1
}
func IsQueued (result Result ) bool {
return result > Canceled
}
func SAdd (states ...S ) S {
if len (states ) == 0 {
return S {}
}
s := slices .Clone (states [0 ])
for i := 1 ; i < len (states ); i ++ {
s = append (s , states [i ]...)
}
return slicesUniq (s )
}
func SRem (src S , states ...S ) S {
s := slices .Clone (src )
if len (states ) == 0 {
return s
}
for i := 1 ; i < len (states ); i ++ {
for ii := 0 ; ii < len (states [i ]); ii ++ {
s = slicesWithout (s , states [i ][ii ])
}
}
return s
}
func StateAdd (source State , overlay State ) State {
s := cloneState (source )
o := cloneState (overlay )
if o .Auto {
s .Auto = true
}
if o .Multi {
s .Multi = true
}
if o .Add != nil {
s .Add = SAdd (s .Add , o .Add )
}
if o .Remove != nil {
s .Remove = SAdd (s .Remove , o .Remove )
}
if o .Require != nil {
s .Require = SAdd (s .Require , o .Require )
}
if o .After != nil {
s .After = SAdd (s .After , o .After )
}
return s
}
func StateSet (source State , auto , multi bool , overlay State ) State {
s := cloneState (source )
o := cloneState (overlay )
s .Auto = auto
s .Multi = multi
if o .Add != nil {
s .Add = o .Add
}
if o .Remove != nil {
s .Remove = o .Remove
}
if o .Require != nil {
s .Require = o .Require
}
if o .After != nil {
s .After = o .After
}
return s
}
func SchemaMerge (schemas ...Schema ) Schema {
l := len (schemas )
switch l {
case 0 :
return Schema {}
case 1 :
return schemas [0 ]
}
ret := make (Schema )
for i := 0 ; i < l ; i ++ {
maps .Copy (ret , schemas [i ])
}
return CloneSchema (ret )
}
func EnvLogLevel (name string ) LogLevel {
if name == "" {
name = EnvAmLog
}
v , _ := strconv .Atoi (os .Getenv (name ))
return LogLevel (v )
}
func ListHandlers (handlers any , states S ) ([]string , error ) {
var methodNames []string
var errs []error
check := func (method string ) {
s1 , s2 := IsHandler (states , method )
if s1 != "" && !slices .Contains (states , s1 ) {
errs = append (errs , fmt .Errorf (
"%w: %s from handler %s" , ErrStateMissing , s1 , method ))
}
if s2 != "" && !slices .Contains (states , s2 ) {
errs = append (errs , fmt .Errorf (
"%w: %s from handler %s" , ErrStateMissing , s2 , method ))
}
if s1 != "" || (method == HandlerAnyEnter || method == HandlerAnyState ) {
methodNames = append (methodNames , method )
}
}
t := reflect .TypeOf (handlers )
for i := 0 ; i < t .NumMethod (); i ++ {
method := t .Method (i ).Name
check (method )
}
val := reflect .ValueOf (handlers ).Elem ()
typ := val .Type ()
for i := 0 ; i < val .NumField (); i ++ {
kind := typ .Field (i ).Type .Kind ()
if kind != reflect .Func {
continue
}
method := typ .Field (i ).Name
check (method )
}
return methodNames , errors .Join (errs ...)
}
var handlerSuffixes = []string {
SuffixEnter , SuffixExit , SuffixState , SuffixEnd , StateAny ,
}
func IsHandler (states S , method string ) (string , string ) {
if method == HandlerAnyEnter || method == HandlerAnyState {
return "" , ""
}
for _ , suffix := range handlerSuffixes {
if strings .HasSuffix (method , suffix ) && len (method ) != len (suffix ) &&
method != StateAny +suffix {
return method [0 : len (method )-len (suffix )], ""
}
}
if strings .HasPrefix (method , StateAny ) && len (method ) != len (StateAny ) &&
method != StateAny +SuffixState {
return method [len (StateAny ):], ""
}
for _ , s := range states {
if !strings .HasPrefix (method , s ) {
continue
}
for _ , ss := range states {
if s +ss == method {
return s , ss
}
}
}
return "" , ""
}
func MockClock (mach *Machine , clock Clock ) {
mach .clock = clock
}
func AMerge [K comparable , V any ](maps ...map [K ]V ) map [K ]V {
out := map [K ]V {}
for _ , m := range maps {
for k , v := range m {
out [k ] = v
}
}
return out
}
func TruncateStr (s string , maxLength int ) string {
if len (s ) <= maxLength {
return s
}
if maxLength < 5 {
return s [:maxLength ]
} else {
return s [:maxLength -3 ] + "..."
}
}
func IndexToTime (index S , active []int ) Time {
ret := make (Time , len (index ))
for _ , i := range active {
ret [i ] = 1
}
return ret
}
func IndexToStates (index S , states []int ) S {
ret := make (S , len (states ))
for i := range states {
name := "unknown" + strconv .Itoa (states [i ])
if len (index ) > states [i ] && states [i ] != -1 {
name = index [states [i ]]
}
ret [i ] = name
}
return ret
}
func StatesToIndex (index S , states S ) []int {
ret := make ([]int , len (states ))
for i := range states {
ret [i ] = slices .Index (index , states [i ])
}
return ret
}
func j(states []string ) string {
return strings .Join (states , " " )
}
func jw(states []string , sep string ) string {
return strings .Join (states , sep )
}
func closeSafe[T any ](ch chan T ) {
select {
case <- ch :
default :
close (ch )
}
}
func padString(str string , length int , pad string ) string {
for {
str += pad
if len (str ) > length {
return str [0 :length ]
}
}
}
func ParseSchema (schema Schema ) (Schema , error ) {
parsed := CloneSchema (schema )
states := slices .Collect (maps .Keys (schema ))
var errs error
for name , state := range schema {
if slices .Contains (state .Remove , name ) {
state .Remove = slicesWithout (state .Remove , name )
}
for _ , add := range state .Add {
if slices .Contains (state .Remove , add ) {
state .Remove = slicesWithout (state .Remove , add )
} else if !slices .Contains (states , add ) {
state .Add = slicesWithout (state .Add , add )
}
}
if slices .Contains (state .After , name ) {
state .After = slicesWithout (state .After , name )
}
for _ , required := range state .Require {
if slices .Contains (state .Remove , required ) {
errs = errors .Join (errs , fmt .Errorf (
"%w: require-remove conflict for %s to %s" ,
ErrSchema , name , required ))
}
}
for _ , n := range state .Remove {
if !slices .Contains (states , n ) {
state .Remove = slicesWithout (state .Remove , n )
}
}
for _ , n := range state .After {
if !slices .Contains (states , n ) {
state .After = slicesWithout (state .After , n )
}
}
for _ , n := range state .Remove {
if !slices .Contains (states , n ) {
state .Remove = slicesWithout (state .Remove , n )
}
}
parsed [name ] = state
}
return parsed , errs
}
func compareArgs(args1 , args2 A ) bool {
match := true
for k , v := range args2 {
if args1 [k ] != v {
match = false
break
}
}
return match
}
type handlerCall struct {
fn reflect .Value
name string
event *Event
timeout bool
}
func randId() string {
id := make ([]byte , 16 )
_ , err := rand .Read (id )
if err != nil {
return "error"
}
return hex .EncodeToString (id )
}
func slicesWithout[S ~[]E , E comparable ](coll S , el E ) S {
idx := slices .Index (coll , el )
ret := slices .Clone (coll )
if idx == -1 {
return ret
}
return slices .Delete (ret , idx , idx +1 )
}
func slicesNone[S1 ~[]E , S2 ~[]E , E comparable ](col1 S1 , col2 S2 ) bool {
for _ , el := range col2 {
if slices .Contains (col1 , el ) {
return false
}
}
return true
}
func slicesEvery[S1 ~[]E , S2 ~[]E , E comparable ](col1 S1 , col2 S2 ) bool {
for _ , el := range col2 {
if !slices .Contains (col1 , el ) {
return false
}
}
return true
}
func slicesFilter[S ~[]E , E any ](coll S , fn func (item E , i int ) bool ) S {
ret := make (S , 0 , len (coll ))
for i , el := range coll {
if fn (el , i ) {
ret = append (ret , el )
}
}
return ret
}
func slicesReverse[S ~[]E , E any ](coll S ) S {
ret := make (S , len (coll ))
for i := range coll {
ret [i ] = coll [len (coll )-1 -i ]
}
return ret
}
func slicesUniq[T comparable ](coll []T ) []T {
if len (coll ) == 0 {
return []T {}
}
seen := make (map [T ]struct {}, len (coll ))
ret := make ([]T , 0 , len (coll ))
for _ , v := range coll {
if _ , ok := seen [v ]; !ok {
seen [v ] = struct {}{}
ret = append (ret , v )
}
}
return ret
}
func cloneOptions(opts *Opts ) *Opts {
if opts == nil {
return &Opts {}
}
return &Opts {
Id : opts .Id ,
HandlerTimeout : opts .HandlerTimeout ,
DontPanicToException : opts .DontPanicToException ,
DontLogStackTrace : opts .DontLogStackTrace ,
DontLogId : opts .DontLogId ,
Resolver : opts .Resolver ,
LogLevel : opts .LogLevel ,
Tracers : opts .Tracers ,
LogArgs : opts .LogArgs ,
QueueLimit : opts .QueueLimit ,
Parent : opts .Parent ,
ParentId : opts .ParentId ,
Tags : opts .Tags ,
DetectEval : opts .DetectEval ,
}
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .