blob: 7911723e9257929275746111909337acfb6a5ad3 [file] [log] [blame]
// The star2proto command executes a Starlark file and prints a protocol
// message, which it expects to find in a module-level variable named 'result'.
//
// THIS COMMAND IS EXPERIMENTAL AND ITS INTERFACE MAY CHANGE.
package main
// TODO(adonovan): add features to make this a useful tool for querying,
// converting, and building messages in proto, JSON, and YAML.
// - define operations for reading and writing files.
// - support (e.g.) querying a proto file given a '-e expr' flag.
// This will need a convenient way to put the relevant descriptors in scope.
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strings"
starlarkproto "go.starlark.net/lib/proto"
"go.starlark.net/resolve"
"go.starlark.net/starlark"
"go.starlark.net/starlarkjson"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
"google.golang.org/protobuf/reflect/protodesc"
"google.golang.org/protobuf/reflect/protoreflect"
"google.golang.org/protobuf/reflect/protoregistry"
"google.golang.org/protobuf/types/descriptorpb"
)
// flags
var (
outputFlag = flag.String("output", "text", "output format (text, wire, json)")
varFlag = flag.String("var", "result", "the variable to output")
descriptors = flag.String("descriptors", "", "comma-separated list of names of files containing proto.FileDescriptorProto messages")
)
// Starlark dialect flags
func init() {
flag.BoolVar(&resolve.AllowFloat, "fp", true, "allow floating-point numbers")
flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type")
flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions")
flag.BoolVar(&resolve.AllowNestedDef, "nesteddef", resolve.AllowNestedDef, "allow nested def statements")
}
func main() {
log.SetPrefix("star2proto: ")
log.SetFlags(0)
flag.Parse()
if len(flag.Args()) != 1 {
fatalf("requires a single Starlark file name")
}
filename := flag.Args()[0]
// By default, use the linked-in descriptors
// (very few in star2proto, e.g. descriptorpb itself).
pool := protoregistry.GlobalFiles
// Load a user-provided FileDescriptorSet produced by a command such as:
// $ protoc --descriptor_set_out=foo.fds foo.proto
if *descriptors != "" {
var fdset descriptorpb.FileDescriptorSet
for i, filename := range strings.Split(*descriptors, ",") {
data, err := ioutil.ReadFile(filename)
if err != nil {
log.Fatalf("--descriptors[%d]: %s", i, err)
}
// Accumulate into the repeated field of FileDescriptors.
if err := (proto.UnmarshalOptions{Merge: true}).Unmarshal(data, &fdset); err != nil {
log.Fatalf("%s does not contain a proto2.FileDescriptorSet: %v", filename, err)
}
}
files, err := protodesc.NewFiles(&fdset)
if err != nil {
log.Fatalf("protodesc.NewFiles: could not build FileDescriptor index: %v", err)
}
pool = files
}
// Execute the Starlark file.
thread := &starlark.Thread{
Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) },
}
starlarkproto.SetPool(thread, pool)
predeclared := starlark.StringDict{
"proto": starlarkproto.Module,
"json": starlarkjson.Module,
}
globals, err := starlark.ExecFile(thread, filename, nil, predeclared)
if err != nil {
if evalErr, ok := err.(*starlark.EvalError); ok {
fatalf("%s", evalErr.Backtrace())
} else {
fatalf("%s", err)
}
}
// Print the output variable as a message.
// TODO(adonovan): this is clumsy.
// Let the user call print(), or provide an expression on the command line.
result, ok := globals[*varFlag]
if !ok {
fatalf("%s must define a module-level variable named %q", filename, *varFlag)
}
msgwrap, ok := result.(*starlarkproto.Message)
if !ok {
fatalf("got %s, want proto.Message, for %q", result.Type(), *varFlag)
}
msg := msgwrap.Message()
// -output
var marshal func(protoreflect.ProtoMessage) ([]byte, error)
switch *outputFlag {
case "wire":
marshal = proto.Marshal
case "text":
marshal = prototext.MarshalOptions{Multiline: true, Indent: "\t"}.Marshal
case "json":
marshal = protojson.MarshalOptions{Multiline: true, Indent: "\t"}.Marshal
default:
fatalf("unsupported -output format: %s", *outputFlag)
}
data, err := marshal(msg)
if err != nil {
fatalf("%s", err)
}
os.Stdout.Write(data)
}
func fatalf(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, "star2proto: ")
fmt.Fprintf(os.Stderr, format, args...)
fmt.Fprintln(os.Stderr)
os.Exit(1)
}