From 0a2edd9fc47a1cb92822327714dc488b1f05d64d Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Sun, 3 May 2015 15:19:32 -0400 Subject: [PATCH] requirements parsing this shit is really brittle --- lib/doc.go | 61 +++++++++----------------------- lib/doc_test.go | 1 + lib/req.go | 92 +++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 110 insertions(+), 44 deletions(-) create mode 100644 lib/req.go diff --git a/lib/doc.go b/lib/doc.go index f4d2e21..ce38b62 100644 --- a/lib/doc.go +++ b/lib/doc.go @@ -81,15 +81,8 @@ func (d *Doc) Get(path string, dest interface{}) error { dv := reflect.ValueOf(dest) dve := dv.Elem() - return setValue(dve, reflect.ValueOf(v)) -} - -func setValue(dest, src reflect.Value) error { - if dest.Type() == src.Type() { - dest.Set(src) - return nil - } - return fmt.Errorf("src and destination are not of same type. src type: %s, dest type: %s", src.Type().Name(), dest.Type().Name()) + dve.Set(reflect.ValueOf(v)) + return nil } // Fill takes the raw values from the moon document and assigns them to the @@ -101,45 +94,25 @@ func (d *Doc) Fill(dest interface{}) error { return fmt.Errorf("destination is of type %v; a pointer type is required", dt) } - dv := reflect.ValueOf(dest).Elem() - - n := dv.NumField() - for i := 0; i < n; i++ { - field, fv := dv.Type().Field(i), dv.Field(i) - - tag := field.Tag - fopts, err := ReadString(string(tag)) - if err != nil { - return fmt.Errorf("unable to parse options for type %s, field %s: %s", - dv.Type().Name(), field.Name, err) - } - - var fname string - if err := fopts.Get("name", &fname); err != nil { - if _, ok := err.(NoValue); !ok { - return err - } - } - - var frequired bool - if err := fopts.Get("required", &frequired); err != nil { - if _, ok := err.(NoValue); !ok { - return err - } - } - - fdefault := fopts.items["default"] + reqs, err := requirements(dest) + if err != nil { + return fmt.Errorf("unable to gather requirements: %s", err) + } - v, ok := d.items[fname] - if !ok { - if frequired { + dv := reflect.ValueOf(dest).Elem() + for fname, req := range reqs { + fv := dv.FieldByName(fname) + v, ok := d.items[req.name] + if ok { + fv.Set(reflect.ValueOf(v)) + } else { + if req.required { return fmt.Errorf("required field missing: %s", fname) - } else { - fv.Set(reflect.ValueOf(fdefault)) - continue + } + if req.d_fault != nil { + fv.Set(reflect.ValueOf(req.d_fault)) } } - fv.Set(reflect.ValueOf(v)) } return nil } diff --git a/lib/doc_test.go b/lib/doc_test.go index 7e540b2..0a6020c 100644 --- a/lib/doc_test.go +++ b/lib/doc_test.go @@ -41,6 +41,7 @@ func TestFill(t *testing.T) { if err := doc.Fill(&dest); err != nil { t.Error(err) + return } if dest.FirstName != "jordan" { diff --git a/lib/req.go b/lib/req.go new file mode 100644 index 0000000..e6662a5 --- /dev/null +++ b/lib/req.go @@ -0,0 +1,92 @@ +package moon + +import ( + "fmt" + "reflect" +) + +type req struct { + name string // name as it appears in moon config file. Defaults to the field name. + cliName string // name as it appears on the command line + help string // text given in help documentation + required bool // whether or not the option must be configured + d_fault interface{} // default value for when the option is missing + short string // short flag on the command line + long string // long flag on the command line + flag bool // whether or not the variable is a flag. + t reflect.Type +} + +func (r req) validate() error { + if r.name == "" { + return fmt.Errorf("invalid requirement: requirement must have a name") + } + + if r.required && r.flag { + return fmt.Errorf("invalid requirement %s: a flag cannot be required", r.name) + } + + if r.required && r.d_fault != nil { + return fmt.Errorf("invalid requirement %s: a required value cannot have a default", r.name) + } + + return nil +} + +func field2req(field reflect.StructField) (*req, error) { + doc, err := ReadString(string(field.Tag)) + if err != nil { + return nil, fmt.Errorf("unable to parse requirements for field %s: %s", field.Name, err) + } + + req := req{ + name: field.Name, + cliName: field.Name, + t: field.Type, + } + // it's really easy to cause infinite recursion here, since this is used by + // some of the higher up functions, so we're going to hack the document directly + + errors := map[string]error{ + "name": doc.Get("name", &req.name), + "cli_name": doc.Get("cli_name", &req.cliName), + "help": doc.Get("help", &req.help), + "required": doc.Get("required", &req.required), + "default": doc.Get("default", &req.d_fault), + "short": doc.Get("short", &req.short), + "long": doc.Get("long", &req.long), + "flag": doc.Get("flag", &req.flag), + } + + for fname, err := range errors { + if err == nil { + continue + } + if _, ok := err.(NoValue); !ok { + return nil, fmt.Errorf("unable to parse requirement %s: %s", fname, err) + } + } + + return &req, nil +} + +func requirements(dest interface{}) (map[string]req, error) { + dt := reflect.TypeOf(dest) + if dt.Kind() != reflect.Ptr { + return nil, fmt.Errorf("destination is of type %v; a pointer type is required", dt) + } + + dv := reflect.ValueOf(dest).Elem() + n := dv.NumField() + out := make(map[string]req, dv.NumField()) + + for i := 0; i < n; i++ { + field := dv.Type().Field(i) + req, err := field2req(field) + if err != nil { + return nil, fmt.Errorf("unable to gather requireents for field %s: %s", field.Name, err) + } + out[field.Name] = *req + } + return out, nil +}