1*4947cdc7SCole Faust// The star2proto command executes a Starlark file and prints a protocol 2*4947cdc7SCole Faust// message, which it expects to find in a module-level variable named 'result'. 3*4947cdc7SCole Faust// 4*4947cdc7SCole Faust// THIS COMMAND IS EXPERIMENTAL AND ITS INTERFACE MAY CHANGE. 5*4947cdc7SCole Faustpackage main 6*4947cdc7SCole Faust 7*4947cdc7SCole Faust// TODO(adonovan): add features to make this a useful tool for querying, 8*4947cdc7SCole Faust// converting, and building messages in proto, JSON, and YAML. 9*4947cdc7SCole Faust// - define operations for reading and writing files. 10*4947cdc7SCole Faust// - support (e.g.) querying a proto file given a '-e expr' flag. 11*4947cdc7SCole Faust// This will need a convenient way to put the relevant descriptors in scope. 12*4947cdc7SCole Faust 13*4947cdc7SCole Faustimport ( 14*4947cdc7SCole Faust "flag" 15*4947cdc7SCole Faust "fmt" 16*4947cdc7SCole Faust "io/ioutil" 17*4947cdc7SCole Faust "log" 18*4947cdc7SCole Faust "os" 19*4947cdc7SCole Faust "strings" 20*4947cdc7SCole Faust 21*4947cdc7SCole Faust starlarkproto "go.starlark.net/lib/proto" 22*4947cdc7SCole Faust "go.starlark.net/resolve" 23*4947cdc7SCole Faust "go.starlark.net/starlark" 24*4947cdc7SCole Faust "go.starlark.net/starlarkjson" 25*4947cdc7SCole Faust "google.golang.org/protobuf/encoding/protojson" 26*4947cdc7SCole Faust "google.golang.org/protobuf/encoding/prototext" 27*4947cdc7SCole Faust "google.golang.org/protobuf/proto" 28*4947cdc7SCole Faust "google.golang.org/protobuf/reflect/protodesc" 29*4947cdc7SCole Faust "google.golang.org/protobuf/reflect/protoreflect" 30*4947cdc7SCole Faust "google.golang.org/protobuf/reflect/protoregistry" 31*4947cdc7SCole Faust "google.golang.org/protobuf/types/descriptorpb" 32*4947cdc7SCole Faust) 33*4947cdc7SCole Faust 34*4947cdc7SCole Faust// flags 35*4947cdc7SCole Faustvar ( 36*4947cdc7SCole Faust outputFlag = flag.String("output", "text", "output format (text, wire, json)") 37*4947cdc7SCole Faust varFlag = flag.String("var", "result", "the variable to output") 38*4947cdc7SCole Faust descriptors = flag.String("descriptors", "", "comma-separated list of names of files containing proto.FileDescriptorProto messages") 39*4947cdc7SCole Faust) 40*4947cdc7SCole Faust 41*4947cdc7SCole Faust// Starlark dialect flags 42*4947cdc7SCole Faustfunc init() { 43*4947cdc7SCole Faust flag.BoolVar(&resolve.AllowFloat, "fp", true, "allow floating-point numbers") 44*4947cdc7SCole Faust flag.BoolVar(&resolve.AllowSet, "set", resolve.AllowSet, "allow set data type") 45*4947cdc7SCole Faust flag.BoolVar(&resolve.AllowLambda, "lambda", resolve.AllowLambda, "allow lambda expressions") 46*4947cdc7SCole Faust flag.BoolVar(&resolve.AllowNestedDef, "nesteddef", resolve.AllowNestedDef, "allow nested def statements") 47*4947cdc7SCole Faust} 48*4947cdc7SCole Faust 49*4947cdc7SCole Faustfunc main() { 50*4947cdc7SCole Faust log.SetPrefix("star2proto: ") 51*4947cdc7SCole Faust log.SetFlags(0) 52*4947cdc7SCole Faust flag.Parse() 53*4947cdc7SCole Faust if len(flag.Args()) != 1 { 54*4947cdc7SCole Faust fatalf("requires a single Starlark file name") 55*4947cdc7SCole Faust } 56*4947cdc7SCole Faust filename := flag.Args()[0] 57*4947cdc7SCole Faust 58*4947cdc7SCole Faust // By default, use the linked-in descriptors 59*4947cdc7SCole Faust // (very few in star2proto, e.g. descriptorpb itself). 60*4947cdc7SCole Faust pool := protoregistry.GlobalFiles 61*4947cdc7SCole Faust 62*4947cdc7SCole Faust // Load a user-provided FileDescriptorSet produced by a command such as: 63*4947cdc7SCole Faust // $ protoc --descriptor_set_out=foo.fds foo.proto 64*4947cdc7SCole Faust if *descriptors != "" { 65*4947cdc7SCole Faust var fdset descriptorpb.FileDescriptorSet 66*4947cdc7SCole Faust for i, filename := range strings.Split(*descriptors, ",") { 67*4947cdc7SCole Faust data, err := ioutil.ReadFile(filename) 68*4947cdc7SCole Faust if err != nil { 69*4947cdc7SCole Faust log.Fatalf("--descriptors[%d]: %s", i, err) 70*4947cdc7SCole Faust } 71*4947cdc7SCole Faust // Accumulate into the repeated field of FileDescriptors. 72*4947cdc7SCole Faust if err := (proto.UnmarshalOptions{Merge: true}).Unmarshal(data, &fdset); err != nil { 73*4947cdc7SCole Faust log.Fatalf("%s does not contain a proto2.FileDescriptorSet: %v", filename, err) 74*4947cdc7SCole Faust } 75*4947cdc7SCole Faust } 76*4947cdc7SCole Faust 77*4947cdc7SCole Faust files, err := protodesc.NewFiles(&fdset) 78*4947cdc7SCole Faust if err != nil { 79*4947cdc7SCole Faust log.Fatalf("protodesc.NewFiles: could not build FileDescriptor index: %v", err) 80*4947cdc7SCole Faust } 81*4947cdc7SCole Faust pool = files 82*4947cdc7SCole Faust } 83*4947cdc7SCole Faust 84*4947cdc7SCole Faust // Execute the Starlark file. 85*4947cdc7SCole Faust thread := &starlark.Thread{ 86*4947cdc7SCole Faust Print: func(_ *starlark.Thread, msg string) { fmt.Println(msg) }, 87*4947cdc7SCole Faust } 88*4947cdc7SCole Faust starlarkproto.SetPool(thread, pool) 89*4947cdc7SCole Faust predeclared := starlark.StringDict{ 90*4947cdc7SCole Faust "proto": starlarkproto.Module, 91*4947cdc7SCole Faust "json": starlarkjson.Module, 92*4947cdc7SCole Faust } 93*4947cdc7SCole Faust globals, err := starlark.ExecFile(thread, filename, nil, predeclared) 94*4947cdc7SCole Faust if err != nil { 95*4947cdc7SCole Faust if evalErr, ok := err.(*starlark.EvalError); ok { 96*4947cdc7SCole Faust fatalf("%s", evalErr.Backtrace()) 97*4947cdc7SCole Faust } else { 98*4947cdc7SCole Faust fatalf("%s", err) 99*4947cdc7SCole Faust } 100*4947cdc7SCole Faust } 101*4947cdc7SCole Faust 102*4947cdc7SCole Faust // Print the output variable as a message. 103*4947cdc7SCole Faust // TODO(adonovan): this is clumsy. 104*4947cdc7SCole Faust // Let the user call print(), or provide an expression on the command line. 105*4947cdc7SCole Faust result, ok := globals[*varFlag] 106*4947cdc7SCole Faust if !ok { 107*4947cdc7SCole Faust fatalf("%s must define a module-level variable named %q", filename, *varFlag) 108*4947cdc7SCole Faust } 109*4947cdc7SCole Faust msgwrap, ok := result.(*starlarkproto.Message) 110*4947cdc7SCole Faust if !ok { 111*4947cdc7SCole Faust fatalf("got %s, want proto.Message, for %q", result.Type(), *varFlag) 112*4947cdc7SCole Faust } 113*4947cdc7SCole Faust msg := msgwrap.Message() 114*4947cdc7SCole Faust 115*4947cdc7SCole Faust // -output 116*4947cdc7SCole Faust var marshal func(protoreflect.ProtoMessage) ([]byte, error) 117*4947cdc7SCole Faust switch *outputFlag { 118*4947cdc7SCole Faust case "wire": 119*4947cdc7SCole Faust marshal = proto.Marshal 120*4947cdc7SCole Faust 121*4947cdc7SCole Faust case "text": 122*4947cdc7SCole Faust marshal = prototext.MarshalOptions{Multiline: true, Indent: "\t"}.Marshal 123*4947cdc7SCole Faust 124*4947cdc7SCole Faust case "json": 125*4947cdc7SCole Faust marshal = protojson.MarshalOptions{Multiline: true, Indent: "\t"}.Marshal 126*4947cdc7SCole Faust 127*4947cdc7SCole Faust default: 128*4947cdc7SCole Faust fatalf("unsupported -output format: %s", *outputFlag) 129*4947cdc7SCole Faust } 130*4947cdc7SCole Faust data, err := marshal(msg) 131*4947cdc7SCole Faust if err != nil { 132*4947cdc7SCole Faust fatalf("%s", err) 133*4947cdc7SCole Faust } 134*4947cdc7SCole Faust os.Stdout.Write(data) 135*4947cdc7SCole Faust} 136*4947cdc7SCole Faust 137*4947cdc7SCole Faustfunc fatalf(format string, args ...interface{}) { 138*4947cdc7SCole Faust fmt.Fprintf(os.Stderr, "star2proto: ") 139*4947cdc7SCole Faust fmt.Fprintf(os.Stderr, format, args...) 140*4947cdc7SCole Faust fmt.Fprintln(os.Stderr) 141*4947cdc7SCole Faust os.Exit(1) 142*4947cdc7SCole Faust} 143