// Mgmt // Copyright (C) James Shubin and the project contributors // Written by James Shubin and the project contributors // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation, either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, see . // // Additional permission under GNU GPL version 3 section 7 // // If you modify this program, or any covered work, by linking or combining it // with embedded mcl code and modules (and that the embedded mcl code and // modules which link with this program, contain a copy of their source code in // the authoritative form) containing parts covered by the terms of any other // license, the licensors of this program grant you additional permission to // convey the resulting work. Furthermore, the licensors of this program grant // the original author, James Shubin, additional permission to update this // additional permission if he deems it necessary to achieve the goals of this // additional permission. package simple import ( "context" "fmt" "reflect" "strings" docsUtil "github.com/purpleidea/mgmt/docs/util" "github.com/purpleidea/mgmt/lang/funcs" "github.com/purpleidea/mgmt/lang/funcs/wrapped" "github.com/purpleidea/mgmt/lang/interfaces" "github.com/purpleidea/mgmt/lang/types" unificationUtil "github.com/purpleidea/mgmt/lang/unification/util" "github.com/purpleidea/mgmt/util/errwrap" ) const ( // DirectInterface specifies whether we should use the direct function // API or not. If we don't use it, then these simple functions are // wrapped with the struct below. DirectInterface = false // XXX: fix any bugs and set to true! ) // RegisteredFuncs maps a function name to the corresponding function scaffold. var RegisteredFuncs = make(map[string]*Scaffold) // must initialize // Info holds some information about this function. type Info struct { Pure bool // is the function pure? (can it be memoized?) Memo bool // should the function be memoized? (false if too much output) Fast bool // is the function slow? (avoid speculative execution) Spec bool // can we speculatively execute it? (true for most) } // Scaffold holds the necessary data to build a (possibly polymorphic) function // with this API. type Scaffold struct { // I is some general info about the function. I *Info // T is the type of the function. It can include unification variables. // At a minimum, this must be a `func(?1) ?2` as a naked `?1` is not // allowed. (TODO: Because of ArgGen.) T *types.Type // C is a check function to run after type unification. It will get // passed the solved type of this function. It should error if this is // not an acceptable option. This function can be omitted. C func(typ *types.Type) error // F is the implementation of the function. The input type can be // determined by inspecting the values. Of note, this API does not tell // the implementation what the correct return type should be. If it // can't be determined from the input types, then a different function // API needs to be used. XXX: Should we extend this here? F interfaces.FuncSig // D is the documentation handle for this function. We look on that // struct or function for the doc string instead of the F field if this // is specified. (This is used for facts.) D interface{} } // Register registers a simple, static, pure, polymorphic function. It is easier // to use than the raw function API. It allows you to build and check a function // based on a type signature that contains unification variables. You may only // specify a single type signature with the API, so some complex patterns are // not possible with this API. Implementing a function like `printf` would not // be possible. Implementing a function which counts the number of elements in a // list would be. func Register(name string, scaffold *Scaffold) { if _, exists := RegisteredFuncs[name]; exists { panic(fmt.Sprintf("a simple func named %s is already registered", name)) } if scaffold == nil { panic("no scaffold specified for simple func") } if scaffold.T == nil { panic("no type specified for simple func") } if scaffold.T.Kind != types.KindFunc { panic("type must be a func") } if scaffold.T.HasVariant() { panic("func contains a variant type signature") } // It's okay if scaffold.C is nil. if scaffold.F == nil { panic("no implementation specified for simple func") } RegisteredFuncs[name] = scaffold // store a copy for ourselves // TODO: Do we need to special case either of these? //if strings.HasPrefix(name, "embedded/") {} //if strings.HasPrefix(name, "golang/") {} var f interface{} = scaffold.F if scaffold.D != nil { // override the doc lookup location if specified f = scaffold.D } metadata, err := funcs.GetFunctionMetadata(f) if err != nil { panic(fmt.Sprintf("could not locate function filename for %s", name)) } funcInfo := &wrapped.Info{} if scaffold.I != nil { funcInfo = &wrapped.Info{ Pure: scaffold.I.Pure, Memo: scaffold.I.Memo, Fast: scaffold.I.Fast, Spec: scaffold.I.Spec, } } // register a copy in the main function database funcs.Register(name, func() interfaces.Func { return &Func{ Metadata: metadata, WrappedFunc: &wrapped.Func{ Name: name, FuncInfo: funcInfo, // NOTE: It might be more correct to Copy here, // but we do the copy inside of ExprFunc.Copy() // instead, so that the same type can be unified // in more than one way. Doing it here wouldn't // be harmful, but it's an extra copy we don't // need to do AFAICT. Type: scaffold.T, // .Copy(), }, Check: scaffold.C, Func: scaffold.F, } }) } // ModuleRegister is exactly like Register, except that it registers within a // named module. This is a helper function. func ModuleRegister(module, name string, scaffold *Scaffold) { Register(module+funcs.ModuleSep+name, scaffold) } // WrappedFunc is a type alias so that we can embed `wrapped.Func` inside our // struct, since the Func name collides with our Func field name. type WrappedFunc = wrapped.Func var _ interfaces.BuildableFunc = &Func{} // ensure it meets this expectation // Func is a scaffolding function struct which fulfills the boiler-plate for the // function API, but that can run a very simple, static, pure, polymorphic // function. type Func struct { *docsUtil.Metadata *WrappedFunc // *wrapped.Func as a type alias to pull in the base impl. // Check is a check function to run after type unification. It will get // passed the solved type of this function. It should error if this is // not an acceptable option. This function can be omitted. Check func(typ *types.Type) error // Func is the implementation of the function. The input type can be // determined by inspecting the values. Of note, this API does not tell // the implementation what the correct return type should be. If it // can't be determined from the input types, then a different function // API needs to be used. XXX: Should we extend this here? Func interfaces.FuncSig } // Build is run to turn the maybe polymorphic, undetermined function, into the // specific statically typed version. It is usually run after unification // completes, and must be run before Info() and any of the other Func interface // methods are used. For this function API, it just runs the Check function to // make sure that the type found during unification is one of the valid ones. func (obj *Func) Build(typ *types.Type) (*types.Type, error) { // typ is the KindFunc signature we're trying to build... if obj.Check != nil { if err := obj.Check(typ); err != nil { return nil, errwrap.Wrapf(err, "can't build %s with %s", obj.Name, typ) } } fn := &types.FuncValue{ T: typ, V: obj.Func, // implementation } obj.Fn = fn return obj.Fn.T, nil } // TypeMatch accepts a list of possible type signatures that we want to check // against after type unification. This helper function returns a function which // is suitable for use in the scaffold check function field. func TypeMatch(typeList []string) func(*types.Type) error { return func(typ *types.Type) error { for _, s := range typeList { t := types.NewType(s) if t == nil { // TODO: should we panic? continue // skip } if unificationUtil.UnifyCmp(typ, t) == nil { return nil } } return fmt.Errorf("did not match") } } // StructRegister takes an CLI args struct with optional struct tags, and // generates simple functions from the contained fields in the specified // namespace. If no struct field named `func` is included, then a default // function name which is the lower case representation of the field name will // be used, otherwise the struct tag contents are used. If the struct tag // contains the `-` character, then the field will be skipped. // TODO: An alternative version of this might choose to return all of the values // as a single giant struct. func StructRegister(moduleName string, args interface{}) error { if args == nil { // programming error return fmt.Errorf("could not convert/access our struct") } //fmt.Printf("A: %+v\n", args) val := reflect.ValueOf(args) if val.Kind() == reflect.Ptr { // max one de-referencing val = val.Elem() } typ := val.Type() for i := 0; i < typ.NumField(); i++ { v := val.Field(i) // value of the field t := typ.Field(i) // struct type, get real type with .Type name := strings.ToLower(t.Name) // default if alias, ok := t.Tag.Lookup("func"); ok { if alias == "-" { // skip continue } name = alias } //fmt.Printf("N: %+v\n", name) // debug if len(strings.Trim(name, "abcdefghijklmnopqrstuvwxyz_")) > 0 { return fmt.Errorf("struct field index(%d) has invalid char(s) in function name", i) } typed, err := types.TypeOf(t.Type) // reflect.Type -> (*types.Type, error) if err != nil { return err } //fmt.Printf("T: %+v\n", typed.String()) // debug ModuleRegister(moduleName, name, &Scaffold{ T: types.NewType(fmt.Sprintf("func() %s", typed.String())), F: func(ctx context.Context, input []types.Value) (types.Value, error) { //if args == nil { // // programming error // return nil, fmt.Errorf("could not convert/access our struct") //} value, err := types.ValueOf(v) // reflect.Value -> (types.Value, error) if err != nil { return nil, errwrap.Wrapf(err, "func `%s.%s()` has nil value", moduleName, name) } //fmt.Printf("V: %+v\n", value) // debug return value, nil }, }) } return nil }