lang: Initial implementation of the mgmt language
This is an initial implementation of the mgmt language. It is a declarative (immutable) functional, reactive, domain specific programming language. It is intended to be a language that is: * safe * powerful * easy to reason about With these properties, we hope this language, and the mgmt engine will allow you to model the real-time systems that you'd like to automate. This also includes a number of other associated changes. Sorry for the large size of this patch.
This commit is contained in:
@@ -28,6 +28,8 @@ import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/purpleidea/mgmt/lang/types"
|
||||
|
||||
errwrap "github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@@ -75,14 +77,12 @@ func B64ToRes(str string) (Res, error) {
|
||||
}
|
||||
b := bytes.NewBuffer(bb)
|
||||
d := gob.NewDecoder(b)
|
||||
err = d.Decode(&output) // pass with &
|
||||
if err != nil {
|
||||
if err := d.Decode(&output); err != nil { // pass with &
|
||||
return nil, errwrap.Wrapf(err, "gob failed to decode")
|
||||
}
|
||||
res, ok := output.(Res)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("output `%v` is not a Res", output)
|
||||
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
@@ -134,6 +134,109 @@ func LowerStructFieldNameToFieldName(res Res) (map[string]string, error) {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LangFieldNameToStructFieldName returns the mapping from lang (AST) field
|
||||
// names to field name as used in the struct. The logic here is a bit strange;
|
||||
// if the resource has struct tags, then it uses those, otherwise it falls back
|
||||
// to using the lower case versions of things. It might be clever to combine the
|
||||
// two so that tagged fields are used as such, and others are used in lowercase,
|
||||
// but this is currently not implemented.
|
||||
// TODO: should this behaviour be changed?
|
||||
func LangFieldNameToStructFieldName(kind string) (map[string]string, error) {
|
||||
res, err := NewResource(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapping, err := StructTagToFieldName(res)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "resource kind `%s` has bad field mapping", kind)
|
||||
}
|
||||
if len(mapping) == 0 { // if no `lang` tags exist, get them automatically
|
||||
mapping, err = LowerStructFieldNameToFieldName(res)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "resource kind `%s` has bad automatic field mapping", kind)
|
||||
}
|
||||
}
|
||||
|
||||
return mapping, nil // lang field name -> field name
|
||||
}
|
||||
|
||||
// StructKindToFieldNameTypeMap returns a map from field name to expected type
|
||||
// in the lang type system.
|
||||
func StructKindToFieldNameTypeMap(kind string) (map[string]*types.Type, error) {
|
||||
res, err := NewResource(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sv := reflect.ValueOf(res).Elem() // pointer to struct, then struct
|
||||
if k := sv.Kind(); k != reflect.Struct {
|
||||
return nil, fmt.Errorf("expected struct, got: %s", k)
|
||||
}
|
||||
|
||||
result := make(map[string]*types.Type)
|
||||
|
||||
st := reflect.TypeOf(res).Elem() // pointer to struct, then struct
|
||||
for i := 0; i < st.NumField(); i++ {
|
||||
field := st.Field(i)
|
||||
name := field.Name
|
||||
// TODO: in future, skip over fields that don't have a `lang` tag
|
||||
//if name == "BaseRes" { // TODO: hack!!!
|
||||
// continue
|
||||
//}
|
||||
|
||||
typ, err := types.TypeOf(field.Type)
|
||||
// some types (eg complex64) aren't convertible, so skip for now...
|
||||
if err != nil {
|
||||
continue
|
||||
//return nil, errwrap.Wrapf(err, "could not identify type of field `%s`", name)
|
||||
}
|
||||
result[name] = typ
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// LangFieldNameToStructType returns the mapping from lang (AST) field names,
|
||||
// and the expected type in our type system for each.
|
||||
func LangFieldNameToStructType(kind string) (map[string]*types.Type, error) {
|
||||
// returns a mapping between fieldName and expected *types.Type
|
||||
fieldNameTypMap, err := StructKindToFieldNameTypeMap(kind)
|
||||
if err != nil {
|
||||
return nil, errwrap.Wrapf(err, "could not determine types for `%s` resource", kind)
|
||||
}
|
||||
|
||||
mapping, err := LangFieldNameToStructFieldName(kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// transform from field name to tag name
|
||||
typMap := make(map[string]*types.Type)
|
||||
for name, typ := range fieldNameTypMap {
|
||||
if strings.Title(name) != name {
|
||||
continue // skip private fields
|
||||
}
|
||||
|
||||
found := false
|
||||
for k, v := range mapping {
|
||||
if v != name {
|
||||
continue
|
||||
}
|
||||
// found
|
||||
if found { // previously found!
|
||||
return nil, fmt.Errorf("duplicate mapping for: %s", name)
|
||||
}
|
||||
typMap[k] = typ
|
||||
found = true // :)
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("could not find mapping for: %s", name)
|
||||
}
|
||||
}
|
||||
|
||||
return typMap, nil
|
||||
}
|
||||
|
||||
// GetUID returns the UID of an user. It supports an UID or an username. Caller
|
||||
// should first check user is not empty. It will return an error if it can't
|
||||
// lookup the UID or username.
|
||||
|
||||
Reference in New Issue
Block a user