diff --git a/cmd/moon/main.go b/cmd/moon/main.go index 376baa5..dab0b02 100644 --- a/cmd/moon/main.go +++ b/cmd/moon/main.go @@ -54,6 +54,19 @@ func to_json(n int) { os.Stdout.Write(b) } +func get() { + docpath := flag.Arg(1) + doc, err := moon.Read(input(2)) + if err != nil { + bail(1, "input error: %s", err) + } + var v interface{} + if err := doc.Get(docpath, &v); err != nil { + bail(1, "error reading value at path %s: %s", docpath, err) + } + fmt.Println(v) +} + func bail(status int, t string, args ...interface{}) { var w io.Writer if status == 0 { @@ -72,8 +85,10 @@ func main() { check() case "to": to() + case "get": + get() case "": - bail(1, "must specify an action.\nvalid actions: check lex parse to") + bail(1, "must specify an action.\nvalid actions: check to get") default: bail(1, "no such action:%s", flag.Arg(0)) } diff --git a/lib/doc.go b/lib/doc.go index f70ee13..b8ff648 100644 --- a/lib/doc.go +++ b/lib/doc.go @@ -2,6 +2,8 @@ package moon import ( "encoding/json" + "fmt" + "reflect" ) type Doc struct { @@ -11,3 +13,24 @@ type Doc struct { func (d *Doc) MarshalJSON() ([]byte, error) { return json.Marshal(d.items) } + +func (d *Doc) Get(path string, dest interface{}) error { + if d.items == nil { + return fmt.Errorf("no item found at path %s (doc is empty)", path) + } + + v, ok := d.items[path] + if !ok { + return fmt.Errorf("no item found at path %s", path) + } + + dt := reflect.TypeOf(dest) + if dt.Kind() != reflect.Ptr { + return fmt.Errorf("destination is of type %v; a pointer type is required", dt) + } + + dv := reflect.ValueOf(dest) + dve := dv.Elem() + dve.Set(reflect.ValueOf(v)) + return nil +} diff --git a/lib/doc_test.go b/lib/doc_test.go new file mode 100644 index 0000000..e876c7a --- /dev/null +++ b/lib/doc_test.go @@ -0,0 +1,23 @@ +package moon + +import ( + "testing" +) + +func TestDoc(t *testing.T) { + doc, err := ReadString(` + name: "jordan" + `) + if err != nil { + t.Error(err) + return + } + var name string + if err := doc.Get("name", &name); err != nil { + t.Error(err) + return + } + if name != "jordan" { + t.Errorf("unexpected name value, expected 'jordan', saw '%s'", name) + } +} diff --git a/lib/parse.go b/lib/parse.go index 1da81f4..76a326e 100644 --- a/lib/parse.go +++ b/lib/parse.go @@ -1,6 +1,7 @@ package moon import ( + "bytes" "fmt" "io" "strings" @@ -8,6 +9,10 @@ import ( const () +// Reads a moon document from a given io.Reader. The io.Reader is advanced to +// EOF. The reader is not closed after reading, since it's an io.Reader and not +// an io.ReadCloser. In the event of error, the state that the source reader +// will be left in is undefined. func Read(r io.Reader) (*Doc, error) { tree, err := parse(r) if err != nil { @@ -25,6 +30,18 @@ func Read(r io.Reader) (*Doc, error) { return &Doc{items: ctx}, nil } +// Reads a moon document from a string. This is purely a convenience method; +// all it does is create a buffer and call the moon.Read function. +func ReadString(source string) (*Doc, error) { + return Read(strings.NewReader(source)) +} + +// Reads a moon document from a slice of bytes. This is purely a concenience +// method; like ReadString, it simply creates a buffer and calls moon.Read +func ReadBytes(b []byte) (*Doc, error) { + return Read(bytes.NewBuffer(b)) +} + func parse(r io.Reader) (node, error) { p := &parser{ root: newRootNode(),