Repository: googleapis/gapic-generator-go
Pull request #24 (2018-10-03 22:47:29)

sample: accept CLI arguments
Only strings work for now and argument names are bad.
We can fix this in a follow up.


Patch:
diff --git a/cmd/gen-go-sample/gapic.go b/cmd/gen-go-sample/gapic.go
index 02fe7ec..5a6b22c 100644
--- a/cmd/gen-go-sample/gapic.go
+++ b/cmd/gen-go-sample/gapic.go
@@ -37,7 +37,13 @@ type SampleValueSet struct {
 }
 
 type SampleParameter struct {
-	Defaults []string
+	Defaults   []string
+	Attributes []SampleAttribute
+}
+
+type SampleAttribute struct {
+	Parameter      string
+	SampleArgument bool `yaml:"sample_argument"`
 }
 
 type GAPICSample struct {
diff --git a/cmd/gen-go-sample/inittree.go b/cmd/gen-go-sample/inittree.go
index 912cbc0..be5bcec 100644
--- a/cmd/gen-go-sample/inittree.go
+++ b/cmd/gen-go-sample/inittree.go
@@ -117,56 +117,22 @@ func (t *initTree) get(k string, info pbinfo.Info) (*initTree, error) {
 // Just do simple structs for now.
 // TODO(pongad): allow map and array index.
 //
-// spec = ident { '.' ident } [ '=' value ] .
-func (t *initTree) Parse(txt string, info pbinfo.Info) error {
-	var sc scanner.Scanner
-
+// spec = path [ '=' value ] .
+func (t *initTree) parseInit(txt string, info pbinfo.Info) error {
 	// The first ident is treated specially to be a member of the root.
 	// Since we know the root is a struct, a dot is the only legal token anyway,
 	// so just insert a dot here so we don't need to treat the first token specially.
-	sc.Init(strings.NewReader("." + txt))
-	sc.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings
+	sc, report := initScanner("." + txt)
 
-	// Scanner reports error by calling sc.Error; we save the error so we always report
-	// the first.
-	var err error
-	report := func(e error) error {
-		if err == nil {
-			err = e
-		}
-		return err
-	}
-	sc.Error = func(_ *scanner.Scanner, msg string) {
-		e := errors.E(nil, msg)
-		e = errors.E(e, "while scaning: %q", txt)
-		report(e)
+	t, r, err := t.parsePath(sc, info)
+	if err != nil {
+		return report(err)
 	}
-
-	for {
-		switch sc.Scan() {
-		case '=':
-			goto equal
-		case scanner.EOF:
-			// TODO: set t.leafVal to zero value of type.
-			return report(nil)
-
-		case '.':
-			if sc.Scan() != scanner.Ident {
-				return report(errors.E(nil, "expected ident, found %q", sc.TokenText()))
-			}
-			if t2, err := t.get(sc.TokenText(), info); err != nil {
-				return report(err)
-			} else {
-				t = t2
-			}
-
-		default:
-			return report(errors.E(nil, "unexpected %q", sc.TokenText()))
-		}
+	if r != '=' {
+		return report(errors.E(nil, "expected '='"))
 	}
 
 	// TODO(pongad): handle resource names
-equal:
 	switch r := sc.Scan(); r {
 	case scanner.Int, scanner.Float, scanner.String, scanner.Ident:
 		if lv := t.leafVal; lv != "" {
@@ -189,7 +155,66 @@ equal:
 	if sc.Scan() != scanner.EOF {
 		return report(errors.E(nil, "expected EOF, found %q", sc.TokenText()))
 	}
-	return err
+	return report(nil)
+}
+
+func (t *initTree) parseSampleArgPath(txt string, info pbinfo.Info, varName string) (*initTree, error) {
+	sc, report := initScanner("." + txt)
+	t, _, err := t.parsePath(sc, info)
+	if err != nil {
+		return nil, report(err)
+	}
+
+	var cp initTree
+	cp = *t
+	*t = initTree{leafVal: varName}
+
+	return &cp, report(nil)
+}
+
+// parsePath parses a path as defined below. It returns the subtree specified by the path,
+// the last scanned token, and any error.
+//
+// path = ident { '.' ident } .
+func (t *initTree) parsePath(sc *scanner.Scanner, info pbinfo.Info) (*initTree, rune, error) {
+	for {
+		switch r := sc.Scan(); r {
+		case '.':
+			if r := sc.Scan(); r != scanner.Ident {
+				return nil, r, errors.E(nil, "expected ident, found %q", sc.TokenText())
+			}
+			if t2, err := t.get(sc.TokenText(), info); err != nil {
+				return nil, 0, err
+			} else {
+				t = t2
+			}
+
+		default:
+			return t, r, nil
+		}
+	}
+}
+
+func initScanner(s string) (*scanner.Scanner, func(error) error) {
+	var sc scanner.Scanner
+	sc.Init(strings.NewReader(s))
+	sc.Mode = scanner.ScanIdents | scanner.ScanInts | scanner.ScanFloats | scanner.ScanStrings
+
+	// Scanner reports error by calling sc.Error; we save the error so we always report
+	// the first.
+	var err error
+	report := func(e error) error {
+		if err == nil {
+			err = e
+		}
+		return err
+	}
+	sc.Error = func(_ *scanner.Scanner, msg string) {
+		e := errors.E(nil, msg)
+		e = errors.E(e, "while scaning: %q", s)
+		report(e)
+	}
+	return &sc, report
 }
 
 func (t *initTree) Print(w io.Writer, g *generator) error {
diff --git a/cmd/gen-go-sample/main.go b/cmd/gen-go-sample/main.go
index 35ff17b..92d4728 100644
--- a/cmd/gen-go-sample/main.go
+++ b/cmd/gen-go-sample/main.go
@@ -101,10 +101,10 @@ func main() {
 
 					gen.reset()
 					if err := gen.genSample(iface.Name, meth.Name, sam.RegionTag, vs); err != nil {
-						log.Fatal(errors.E(err, "value set: %q", vs))
+						log.Fatal(errors.E(err, "value set: %q", vs.ID))
 					}
 					if err := gen.commit(!*nofmt, time.Now().Year(), os.Stdout); err != nil {
-						log.Fatal(errors.E(err, "can't commit value set: %q", vs))
+						log.Fatal(errors.E(err, "can't commit value set: %q", vs.ID))
 					}
 				}
 			}
@@ -196,18 +196,6 @@ func (g *generator) genSample(ifaceName, methName, regTag string, valSet SampleV
 		return errors.E(nil, "service %q doesn't have method %q", serv.GetName(), methName)
 	}
 
-	p := g.pt.Printf
-
-	p("// [START %s]", regTag)
-	p("")
-
-	p("func sample%s() {", methName)
-	p("  ctx := context.Background()")
-	p("  c := %s.New%sClient(ctx)", g.clientPkg.Name, pbinfo.ReduceServName(serv.GetName(), g.clientPkg.Name))
-	p("")
-	g.imports[pbinfo.ImportSpec{Path: "context"}] = true
-
-	// TODO(pongad): properly create the request.
 	inType := g.descInfo.Type[meth.GetInputType()]
 	if inType == nil {
 		return errors.E(nil, "can't find input type %q", meth.GetInputType())
@@ -217,14 +205,67 @@ func (g *generator) genSample(ifaceName, methName, regTag string, valSet SampleV
 		return errors.E(err, "can't import input type: %q", inType)
 	}
 
+	var (
+		argNames []string
+		argTrees []*initTree
+	)
+
 	itree := initTree{
 		typ: initType{desc: inType},
 	}
 	for _, def := range valSet.Parameters.Defaults {
-		if err := itree.Parse(def, g.descInfo); err != nil {
+		if err := itree.parseInit(def, g.descInfo); err != nil {
 			return errors.E(err, "can't set default value: %q", def)
 		}
 	}
+	for i, attr := range valSet.Parameters.Attributes {
+		if !attr.SampleArgument {
+			continue
+		}
+		name := fmt.Sprintf("arg%d", i)
+		subTree, err := itree.parseSampleArgPath(attr.Parameter, g.descInfo, name)
+		if err != nil {
+			return errors.E(err, "can't set sample function argument: %q", attr.Parameter)
+		}
+
+		argNames = append(argNames, name)
+		argTrees = append(argTrees, subTree)
+	}
+
+	p := g.pt.Printf
+
+	p("// [START %s]", regTag)
+	p("")
+
+	var argStr string
+	if len(argNames) > 0 {
+		argStr = strings.Join(argNames, ",") + " string"
+	}
+
+	p("func sample%s(%s) {", methName, argStr)
+	p("  ctx := context.Background()")
+	p("  c := %s.New%sClient(ctx)", g.clientPkg.Name, pbinfo.ReduceServName(serv.GetName(), g.clientPkg.Name))
+	p("")
+	g.imports[pbinfo.ImportSpec{Path: "context"}] = true
+
+	for i, name := range argNames {
+		var sb strings.Builder
+		fmt.Fprintf(&sb, "// %s := ", name)
+		if err := argTrees[i].Print(&sb, g); err != nil {
+			return err
+		}
+		s := sb.String()
+		s = strings.Replace(s, "\n", "\n//", -1)
+
+		w := g.pt.Writer()
+		if _, err := w.Write([]byte(s)); err != nil {
+			return err
+		}
+		if _, err := w.Write([]byte{'\n'}); err != nil {
+			return err
+		}
+	}
+
 	{
 		w := g.pt.Writer()
 
@@ -254,11 +295,25 @@ func (g *generator) genSample(ifaceName, methName, regTag string, valSet SampleV
 	p("")
 
 	p("func main() {")
-	p("  sample%s()", methName)
+	p("  flag.Parse()")
+	p("  sample%s(%s)", methName, flagArgs(len(argNames)))
 	p("}")
 	p("")
 
 	g.imports[inSpec] = true
 	g.imports[pbinfo.ImportSpec{Path: "fmt"}] = true
+	g.imports[pbinfo.ImportSpec{Path: "flag"}] = true
 	return nil
 }
+
+func flagArgs(n int) string {
+	if n == 0 {
+		return ""
+	}
+
+	var sb strings.Builder
+	for i := 0; i < n; i++ {
+		fmt.Fprintf(&sb, ", flag.Arg(%d)", i)
+	}
+	return sb.String()[2:]
+}


Test patch:
diff --git a/cmd/gen-go-sample/inittree_test.go b/cmd/gen-go-sample/inittree_test.go
index d9b1880..26b8883 100644
--- a/cmd/gen-go-sample/inittree_test.go
+++ b/cmd/gen-go-sample/inittree_test.go
@@ -57,7 +57,7 @@ func TestTree(t *testing.T) {
 		typ: initType{desc: info.Type["RootType"]},
 	}
 	for _, s := range specs {
-		if err := root.Parse(s, info); err != nil {
+		if err := root.parseInit(s, info); err != nil {
 			t.Fatal(err)
 		}
 	}
@@ -106,7 +106,7 @@ testcase:
 	} {
 		root := initTree{typ: initType{desc: info.Type["RootType"]}}
 		for _, txt := range tst {
-			if root.Parse(txt, info) != nil {
+			if root.parseInit(txt, info) != nil {
 				continue testcase
 			}
 		}
diff --git a/cmd/gen-go-sample/main_test.go b/cmd/gen-go-sample/main_test.go
index 260bcd5..e5bd016 100644
--- a/cmd/gen-go-sample/main_test.go
+++ b/cmd/gen-go-sample/main_test.go
@@ -122,6 +122,9 @@ func TestSample(t *testing.T) {
 				`e = FOO`,
 				`f = "in a oneof"`,
 			},
+			Attributes: []SampleAttribute{
+				{"a.x", true},
+			},
 		},
 	}
 	if err := g.genSample("MyService", "MyMethod", "awesome_region", vs); err != nil {
diff --git a/cmd/gen-go-sample/testdata/sample.want b/cmd/gen-go-sample/testdata/sample.want
index 270957a..56559c5 100644
--- a/cmd/gen-go-sample/testdata/sample.want
+++ b/cmd/gen-go-sample/testdata/sample.want
@@ -17,19 +17,21 @@
 package main
 import(
  "context"
+ "flag"
  "fmt"
 foo "path/to/client/foo"
 foopb "path/to/pb/foo"
 )
 // [START awesome_region]
 
-func sampleMyMethod() {
+func sampleMyMethod(arg0 string) {
 	ctx := context.Background()
 	c := foo.NewClient(ctx)
 
+// arg0 := 42
 req := &foopb.InputType{
 	A: &foopb.AType{
-		X: 42,
+		X: arg0,
 		Y: 3.14159,
 	},
 	B: "foobar",
@@ -48,6 +50,7 @@ req := &foopb.InputType{
 // [END awesome_region]
 
 func main() {
-	sampleMyMethod()
+	flag.Parse()
+	sampleMyMethod(flag.Arg(0))
 }