diff --git a/cmd/gen-go-sample/inittree.go b/cmd/gen-go-sample/inittree.go new file mode 100644 index 00000000000..66071d79fef --- /dev/null +++ b/cmd/gen-go-sample/inittree.go @@ -0,0 +1,158 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "bufio" + "io" + "strings" + "text/scanner" + "unicode" + + "github.com/googleapis/gapic-generator-go/internal/errors" +) + +// initTree represents a node in the initialization tree. +type initTree struct { + // T is either a composite value (struct/array/map), where keys and vals are set, + // or a simple value, where leafVal is set. + + // Use array representation; we need order, and we probably won't + // have many pairs anyway. + keys []string + vals []*initTree + + // Text of the literal. If the literal is a string, it's already quoted. + leafVal string +} + +func (t *initTree) get(k string) *initTree { + for i, key := range t.keys { + if k == key { + return t.vals[i] + } + } + + v := new(initTree) + t.keys = append(t.keys, k) + t.vals = append(t.vals, v) + return v +} + +func (t *initTree) Parse(txt string) error { + var sc scanner.Scanner + sc.Init(strings.NewReader(txt)) + 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", txt) + report(e) + } + + // Just do simple structs for now. + // TODO(pongad): allow map and array index. + // spec = ident { '.' ident } [ '=' value ] . + + if sc.Scan() != scanner.Ident { + return report(errors.E(nil, "expected ident, found %q", sc.TokenText())) + } + t = t.get(sc.TokenText()) + + 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())) + } + t = t.get(sc.TokenText()) + + default: + return report(errors.E(nil, "unexpected %q", sc.TokenText())) + } + } + + // TODO(pongad): check that value and type match + // TODO(pongad): check that we don't write two values onto the same node + // TODO(pongad): handle resource names + // TODO(pongad): properly validate and print enums +equal: + switch r := sc.Scan(); r { + case scanner.Int, scanner.Float, scanner.String, scanner.Ident: + t.leafVal = sc.TokenText() + default: + return report(errors.E(nil, "expected value, found %q", sc.TokenText())) + } + + if sc.Scan() != scanner.EOF { + return report(errors.E(nil, "expected EOF, found %q", sc.TokenText())) + } + return err +} + +func (t *initTree) Print(w io.Writer) error { + bw := bufio.NewWriter(w) + t.print(bw, 1) + return bw.Flush() +} + +func (t *initTree) print(w *bufio.Writer, ind int) { + if v := t.leafVal; v != "" { + w.WriteString(v) + return + } + + // TODO(pongad): Figure out how to print type. + w.WriteString("TYPE{\n") + for i, k := range t.keys { + for i := 0; i < ind; i++ { + w.WriteByte('\t') + } + snakeToCapCamel(w, k) + w.WriteString(": ") + t.vals[i].print(w, ind+1) + w.WriteString(",\n") + } + w.WriteString("}") +} + +func snakeToCapCamel(w *bufio.Writer, s string) { + cap := true + for _, r := range s { + if r == '_' { + cap = true + } else if cap { + cap = false + w.WriteRune(unicode.ToUpper(r)) + } else { + w.WriteRune(r) + } + } +} diff --git a/cmd/gen-go-sample/inittree_test.go b/cmd/gen-go-sample/inittree_test.go new file mode 100644 index 00000000000..cf186659ed8 --- /dev/null +++ b/cmd/gen-go-sample/inittree_test.go @@ -0,0 +1,65 @@ +// Copyright 2018 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package main + +import ( + "strings" + "testing" +) + +func TestTree(t *testing.T) { + specs := []string{ + `a.b = 1`, + `a.c = "xyz"`, + `a.d = 2.718281828`, + `x = 123`, + } + + var root initTree + for _, s := range specs { + if err := root.Parse(s); err != nil { + t.Fatal(err) + } + } + + for _, tst := range []struct { + path []string + val string + }{ + {[]string{"a", "b"}, "1"}, + {[]string{"a", "c"}, `"xyz"`}, + {[]string{"a", "d"}, "2.718281828"}, + {[]string{"x"}, "123"}, + } { + node := &root + for _, p := range tst.path { + node = node.get(p) + } + if node.leafVal != tst.val { + t.Errorf("%s = %q, want %q", strings.Join(tst.path, "->"), node.leafVal, tst.val) + } + } + + if t.Failed() { + t.SkipNow() + } + + var buf strings.Builder + buf.WriteByte('\n') + if err := root.Print(&buf); err != nil { + t.Error(err) + } + t.Log(buf.String()) +} diff --git a/cmd/gen-go-sample/main.go b/cmd/gen-go-sample/main.go index e364b64df8f..7a75c7ad1f9 100644 --- a/cmd/gen-go-sample/main.go +++ b/cmd/gen-go-sample/main.go @@ -97,7 +97,7 @@ func main() { log.Fatal(errors.E(err, "value set: %q", vs)) } if err := gen.commit(!*nofmt); err != nil { - log.Fatal(errors.E(err, "value set: %q", vs)) + log.Fatal(errors.E(err, "can't commit value set: %q", vs)) } } } @@ -161,7 +161,7 @@ func (g *generator) commit(gofmt bool) error { if gofmt { b2, err := format.Source(b) if err != nil { - return err + return errors.E(err, "syntax error, run with -nofmt to find out why?") } b = b2 } @@ -214,11 +214,25 @@ func (g *generator) genSample(ifaceName, methName, regTag string, valSet SampleV return errors.E(err, "can't import input type: %q", inType) } - p(" req := %s.%s{", inSpec.Name, inType.GetName()) + var itree initTree for _, def := range valSet.Parameters.Defaults { - p("// %s", def) + if err := itree.Parse(def); err != nil { + return errors.E(err, "can't set default value: %q", def) + } + } + { + w := g.pt.Writer() + + if _, err := w.Write([]byte("req := ")); err != nil { + return err + } + if err := itree.Print(g.pt.Writer()); err != nil { + return err + } + if _, err := w.Write([]byte{'\n'}); err != nil { + return err + } } - p(" }") // TODO(pongad): handle non-unary p(" resp, err := c.%s(ctx, req)", methName) diff --git a/cmd/gen-go-sample/main_test.go b/cmd/gen-go-sample/main_test.go index 251c3faddf9..e67903b62fb 100644 --- a/cmd/gen-go-sample/main_test.go +++ b/cmd/gen-go-sample/main_test.go @@ -84,7 +84,11 @@ func TestSample(t *testing.T) { vs := SampleValueSet{ ID: "my_value_set", Parameters: SampleParameter{ - Defaults: []string{"foo=bar"}, + Defaults: []string{ + `a.x = 42`, + `a.y = 3.14159`, + `b = "foobar"`, + }, }, } if err := g.genSample("MyService", "MyMethod", "awesome_region", vs); err != nil { diff --git a/cmd/gen-go-sample/test.bash b/cmd/gen-go-sample/test.bash index 3bf722103a7..9104ac5c222 100755 --- a/cmd/gen-go-sample/test.bash +++ b/cmd/gen-go-sample/test.bash @@ -25,6 +25,6 @@ if [ -z $COMMON_PROTO ]; then exit 1 fi -./gen-go-sample \ +./gen-go-sample $* \ -gapic "$GOOGLEAPIS/google/cloud/language/v1/language_gapic.yaml" \ -desc <(protoc -o /dev/stdout --include_imports -I "$COMMON_PROTO" -I "$GOOGLEAPIS" "$GOOGLEAPIS"/google/cloud/language/v1/*.proto) diff --git a/cmd/gen-go-sample/testdata/sample.want b/cmd/gen-go-sample/testdata/sample.want index 4ce6501ee2c..0e1ca8bcdb6 100644 --- a/cmd/gen-go-sample/testdata/sample.want +++ b/cmd/gen-go-sample/testdata/sample.want @@ -4,9 +4,13 @@ func sampleMyMethod() { ctx := context.Background() c := mypackagepb.NewClient(ctx) - req := mypackagepb.InputType{ - // foo=bar - } +req := TYPE{ + A: TYPE{ + X: 42, + Y: 3.14159, +}, + B: "foobar", +} resp, err := c.MyMethod(ctx, req) if err != nil { // TODO: Handle error. diff --git a/internal/printer/printer.go b/internal/printer/printer.go index 42dbee2a56d..9a452d16b2e 100644 --- a/internal/printer/printer.go +++ b/internal/printer/printer.go @@ -18,6 +18,7 @@ package printer import ( "bytes" "fmt" + "io" "strings" ) @@ -65,6 +66,11 @@ func (p *P) Printf(s string, args ...interface{}) { } } +// Writer returns a writer that writes to p's underlying buffer without performing indentation. +func (p *P) Writer() io.Writer { + return &p.buf +} + // Bytes returns the bytes written by Printf. It is valid up to the next call to Printf or Reset. func (p *P) Bytes() []byte { return p.buf.Bytes()