| Index: tools/proto-gae/proto_gae.go
 | 
| diff --git a/tools/proto-gae/proto_gae.go b/tools/proto-gae/proto_gae.go
 | 
| new file mode 100644
 | 
| index 0000000000000000000000000000000000000000..6db06781b3e1ee3ee3bfcb39f3e37d7f40051e2d
 | 
| --- /dev/null
 | 
| +++ b/tools/proto-gae/proto_gae.go
 | 
| @@ -0,0 +1,160 @@
 | 
| +// Copyright 2015 The Chromium Authors. All rights reserved.
 | 
| +// Use of this source code is governed by a BSD-style license that can be
 | 
| +// found in the LICENSE file.
 | 
| +
 | 
| +package main
 | 
| +
 | 
| +import (
 | 
| +	"bufio"
 | 
| +	"flag"
 | 
| +	"fmt"
 | 
| +	"io"
 | 
| +	"os"
 | 
| +	"sort"
 | 
| +	"strings"
 | 
| +	"text/template"
 | 
| +
 | 
| +	"github.com/luci/luci-go/common/errors"
 | 
| +	"github.com/luci/luci-go/common/flag/stringsetflag"
 | 
| +)
 | 
| +
 | 
| +type app struct {
 | 
| +	out io.Writer
 | 
| +
 | 
| +	packageName string
 | 
| +	typeNames   stringsetflag.Flag
 | 
| +	outFile     string
 | 
| +}
 | 
| +
 | 
| +const help = `Usage of %s:
 | 
| +
 | 
| +%s is a go-generator program that generates PropertyConverter implementations
 | 
| +for types produced by protoc. It can be used in a go generation file like:
 | 
| +
 | 
| +  //go:generate <protoc command>
 | 
| +  //go:generate proto-gae -type MessageType -type OtherMessageType
 | 
| +
 | 
| +This will produce a new file which implements the ToProperty and FromProperty
 | 
| +methods for the named types.
 | 
| +
 | 
| +Options:
 | 
| +`
 | 
| +
 | 
| +func (a *app) parseArgs(fs *flag.FlagSet, args []string) error {
 | 
| +	fs.SetOutput(a.out)
 | 
| +	fs.Usage = func() {
 | 
| +		fmt.Fprintf(a.out, help, args[0], args[0])
 | 
| +		fs.PrintDefaults()
 | 
| +	}
 | 
| +
 | 
| +	fs.Var(&a.typeNames, "type",
 | 
| +		"A generated proto.Message type to generate stubs for (required, repeatable)")
 | 
| +	fs.StringVar(&a.outFile, "out", "proto_gae.gen.go",
 | 
| +		"The name of the output file")
 | 
| +
 | 
| +	if err := fs.Parse(args[1:]); err != nil {
 | 
| +		return err
 | 
| +	}
 | 
| +	fail := errors.MultiError(nil)
 | 
| +	if a.typeNames.Data == nil || a.typeNames.Data.Len() == 0 {
 | 
| +		fail = append(fail, errors.New("must specify one or more -type"))
 | 
| +	}
 | 
| +	if !strings.HasSuffix(a.outFile, ".go") {
 | 
| +		fail = append(fail, errors.New("-output must end with '.go'"))
 | 
| +	}
 | 
| +	if len(fail) > 0 {
 | 
| +		for _, e := range fail {
 | 
| +			fmt.Fprintln(a.out, "error:", e)
 | 
| +		}
 | 
| +		fmt.Fprintln(a.out)
 | 
| +		fs.Usage()
 | 
| +		return fail
 | 
| +	}
 | 
| +	return nil
 | 
| +}
 | 
| +
 | 
| +var tmpl = template.Must(
 | 
| +	template.New("main").Parse(`// AUTOGENERATED: Do not edit
 | 
| +
 | 
| +package {{index . "package"}}
 | 
| +
 | 
| +import (
 | 
| +	"github.com/golang/protobuf/proto"
 | 
| +
 | 
| +	"github.com/luci/gae/service/datastore"
 | 
| +){{range index . "types"}}
 | 
| +
 | 
| +var _ datastore.PropertyConverter = (*{{.}})(nil)
 | 
| +
 | 
| +// ToProperty implements datastore.PropertyConverter. It causes an embedded
 | 
| +// '{{.}}' to serialize to an unindexed '[]byte' when used with the
 | 
| +// "github.com/luci/gae" library.
 | 
| +func (p *{{.}}) ToProperty() (prop datastore.Property, err error) {
 | 
| +	data, err := proto.Marshal(p)
 | 
| +	if err == nil {
 | 
| +		prop.SetValue(data, datastore.NoIndex)
 | 
| +	}
 | 
| +	return
 | 
| +}
 | 
| +
 | 
| +// FromProperty implements datastore.PropertyConverter. It parses a '[]byte'
 | 
| +// into an embedded '{{.}}' when used with the "github.com/luci/gae" library.
 | 
| +func (p *{{.}}) FromProperty(prop datastore.Property) error {
 | 
| +	data, err := prop.Project(datastore.PTBytes)
 | 
| +	if err != nil {
 | 
| +		return err
 | 
| +	}
 | 
| +	return proto.Unmarshal(data.([]byte), p)
 | 
| +}{{end}}
 | 
| +`))
 | 
| +
 | 
| +func (a *app) writeTo(w io.Writer) error {
 | 
| +	typeNames := a.typeNames.Data.ToSlice()
 | 
| +	sort.Strings(typeNames)
 | 
| +
 | 
| +	return tmpl.Execute(w, map[string]interface{}{
 | 
| +		"package": a.packageName,
 | 
| +		"types":   typeNames,
 | 
| +	})
 | 
| +}
 | 
| +
 | 
| +func (a *app) main() {
 | 
| +	if err := a.parseArgs(flag.NewFlagSet(os.Args[0], flag.ContinueOnError), os.Args); err != nil {
 | 
| +		os.Exit(1)
 | 
| +	}
 | 
| +	ofile, err := os.Create(a.outFile)
 | 
| +	if err != nil {
 | 
| +		fmt.Fprintf(a.out, "error: %s", err)
 | 
| +		os.Exit(2)
 | 
| +	}
 | 
| +	closeFn := func(delete bool) {
 | 
| +		if ofile != nil {
 | 
| +			if err := ofile.Close(); err != nil {
 | 
| +				fmt.Fprintf(a.out, "error while closing file: %s", err)
 | 
| +			}
 | 
| +			if delete {
 | 
| +				if err := os.Remove(a.outFile); err != nil {
 | 
| +					fmt.Fprintf(a.out, "failed to remove file!")
 | 
| +				}
 | 
| +			}
 | 
| +		}
 | 
| +		ofile = nil
 | 
| +	}
 | 
| +	defer closeFn(false)
 | 
| +	buf := bufio.NewWriter(ofile)
 | 
| +	err = a.writeTo(buf)
 | 
| +	if err != nil {
 | 
| +		fmt.Fprintf(a.out, "error while writing: %s", err)
 | 
| +		closeFn(true)
 | 
| +		os.Exit(3)
 | 
| +	}
 | 
| +	if err := buf.Flush(); err != nil {
 | 
| +		fmt.Fprintf(a.out, "error while writing: %s", err)
 | 
| +		closeFn(true)
 | 
| +		os.Exit(4)
 | 
| +	}
 | 
| +}
 | 
| +
 | 
| +func main() {
 | 
| +	(&app{out: os.Stderr, packageName: os.Getenv("GOPACKAGE")}).main()
 | 
| +}
 | 
| 
 |