| // 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) |
| } |