From 9a966a37f5a22013f70706791853858193ae410e Mon Sep 17 00:00:00 2001 From: Jordan Orelli Date: Mon, 25 May 2015 20:53:21 -0400 Subject: [PATCH] moar documentation I'm on a train! --- doc.go | 41 +++++++++++++++++++++----- doc_test.go | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++++ parse.go | 75 +++++++++++++++++++++++++++++++++++++++------- req.go | 9 ++---- 4 files changed, 187 insertions(+), 23 deletions(-) diff --git a/doc.go b/doc.go index ce38b62..ab589ee 100644 --- a/doc.go +++ b/doc.go @@ -57,10 +57,36 @@ func (d *Doc) MarshalMoon() ([]byte, error) { return buf.Bytes(), nil } -// retrieve a value from the moon document and assign it to provided -// destination. dest must be a pointer. path may be a path to the desired -// field, e.g., people/1/name would get the name field of the second element of -// the people list. +// Get reads a value from the Moon document at a given path, assigning the +// value to supplied destination pointer. The argument dest MUST be a pointer, +// otherwise Get will be unable to overwrite the value that it (should) point +// to. +// +// path may represent either a top-level key or a path to a value found within the document. +// +// Let's say you have a simple document that contains just a few values: +// name: jordan +// city: brooklyn +// Calling Get("name", &name) would read the value in the document at the +// "name" key and assign it to the location pointed at by the *string name. +// +// Let's take a more complex example: +// @webserver: { +// host: www.example.com +// port: 80 +// } +// +// @mailserver: { +// host: mail.example.com +// port: 25 +// } +// +// servers: [@webserver @mailserver] +// +// Calling Get("servers/1/host", &host) would look a list at the key "servers", +// retrieve the item at index 1 in that list, and then read the field named +// "host" within that item, assigning the value to the address pointed to by +// the *string named host 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) @@ -85,9 +111,10 @@ func (d *Doc) Get(path string, dest interface{}) error { return nil } -// Fill takes the raw values from the moon document and assigns them to the -// struct pointed at by dest. Dest must be a struct pointer; any other type -// for dest will result in an error. +// Fill takes the raw values from the Moon document and assigns them to the +// fields of the struct pointed at by dest. Dest must be a struct pointer; any +// other type for dest will result in an error. Please see the Parse +// documentation for a description of how the values will be filled. func (d *Doc) Fill(dest interface{}) error { dt := reflect.TypeOf(dest) if dt.Kind() != reflect.Ptr { diff --git a/doc_test.go b/doc_test.go index 0a6020c..2c3a71e 100644 --- a/doc_test.go +++ b/doc_test.go @@ -1,6 +1,7 @@ package moon import ( + "fmt" "testing" ) @@ -55,3 +56,87 @@ func TestFill(t *testing.T) { } } + +func ExampleDoc_Fill() { + input := ` + name: jordan + age: 29 + ` + + var config struct { + Name string `name: "name"` + Age int `name: "age"` + City string `name: "city" default: "Brooklyn"` + } + + doc, err := ReadString(input) + if err != nil { + fmt.Printf("error reading input: %s", err) + return + } + + if err := doc.Fill(&config); err != nil { + fmt.Printf("error filling config value: %s", err) + return + } + + fmt.Println(config) + // Output: {jordan 29 Brooklyn} +} + +func ExampleDoc_Get_one() { + input := ` + name: jordan + age: 29 + ` + + doc, err := ReadString(input) + if err != nil { + fmt.Printf("error reading input: %s", err) + return + } + + var name string + if err := doc.Get("name", &name); err != nil { + fmt.Printf("error filling config value: %s", err) + return + } + + fmt.Println(name) + // Output: jordan +} + +func ExampleDoc_Get_two() { + input := ` + @todd: { + name: todd + age: 38 + } + + @sean: { + name: sean + age: 34 + } + + @jordan: { + name: jordan + age: 29 + } + brothers: [@todd @sean @jordan] + ` + + doc, err := ReadString(input) + if err != nil { + fmt.Printf("error reading input: %s", err) + return + } + + var name string + if err := doc.Get("brothers/1/name", &name); err != nil { + fmt.Printf("error filling config value: %s", err) + return + } + + fmt.Println(name) + // Output: sean +} diff --git a/parse.go b/parse.go index 11895aa..b4e6420 100644 --- a/parse.go +++ b/parse.go @@ -21,7 +21,9 @@ var nodes = map[tokenType]func(p *parser) node{ t_duration: func(p *parser) node { return new(durationNode) }, } -var DefaultPath = "./config.moon" +// Static path for configuration file. By default, a call to Parse wil look for +// a file at the path specified by Path. +var Path = "" func bail(status int, t string, args ...interface{}) { if status == 0 { @@ -32,7 +34,55 @@ func bail(status int, t string, args ...interface{}) { os.Exit(status) } -func Parse(dest interface{}) { +// Parse reads in all command line arguments in addition to parsing the Moon +// configuration file found at moon.Path. If moon.Path is not set, Parse does +// not automatically look for a configuration file. +// +// The command-line options as well as the values found in the Moon document +// will be used to fill the destination object pointed to by the dest argument. +// Clients may use struct tags to dictate how values will be filled in the +// destination struct. Within the struct tag, the client may provide a moon +// document describing how the value is to be parsed. +// +// The following tags are currently recognized: +// +// - name: the value's name inside of the moon document +// - help: help text to be printed when running your program as "program help" +// - required: whether or not the specified field is required. +// - default: default value for the given field +// - short: single character to be used as a command-line flag +// - long: a string of characters to be used as a command-line option +// +// Here's an example of a struct definition that is annotated to inform the +// Moon parser how to fill the struct with values from a Moon document. +// +// var config struct { +// Host string ` +// name: host +// help: target host with whom we will connect +// required: true +// short: h +// long: host +// ` +// +// Port int ` +// name: port +// help: port to dial on the target host +// default: 12345 +// short: p +// long: port +// ` +// } +// +// .. and here's how we would parse our command-line arguments and config file +// at path "./config.moon" to fill the struct at config: +// +// moon.Path = "./config" +// moon.Parse(&config) +// +// Any value provied as a command-line argument will override the value +// supplied by the config file at "./config" +func Parse(dest interface{}) *Doc { cliArgs, err := parseArgs(os.Args, dest) if err != nil { bail(1, "unable to parse cli args: %s", err) @@ -40,15 +90,19 @@ func Parse(dest interface{}) { var doc *Doc - f, err := os.Open(DefaultPath) - if err == nil { - defer f.Close() - d, err := Read(f) - if err != nil { - bail(1, "unable to parse moon config file at path %s: %s", DefaultPath, err) + if Path != "" { + f, err := os.Open(Path) + if err == nil { + defer f.Close() + d, err := Read(f) + if err != nil { + bail(1, "unable to parse moon config file at path %s: %s", Path, err) + } + doc = d } - doc = d - } else { + } + + if doc == nil { doc = &Doc{items: make(map[string]interface{})} } @@ -59,6 +113,7 @@ func Parse(dest interface{}) { if err := doc.Fill(dest); err != nil { bail(1, "unable to fill moon config values: %s", err) } + return doc } // Reads a moon document from a given io.Reader. The io.Reader is advanced to diff --git a/req.go b/req.go index ea3f015..8cd2784 100644 --- a/req.go +++ b/req.go @@ -9,7 +9,6 @@ import ( 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 @@ -55,10 +54,9 @@ func field2req(field reflect.StructField) (*req, error) { } req := req{ - name: field.Name, - cliName: field.Name, - t: field.Type, - long: field.Name, + name: field.Name, + t: field.Type, + long: field.Name, } // this is called by Fill, so we have to do Fill's work by hand, otherwise @@ -66,7 +64,6 @@ func field2req(field reflect.StructField) (*req, error) { 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),