1 package main 2 3 import ( 4 "bufio" 5 "fmt" 6 "io" 7 "io/ioutil" 8 "log" 9 "os" 10 "strings" 11 12 "github.com/golang/protobuf/proto" 13 pb "github.com/google/protobuf/examples/tutorial" 14 ) 15 16 func promptForAddress(r io.Reader) (*pb.Person, error) { 17 // A protocol buffer can be created like any struct. 18 p := &pb.Person{} 19 20 rd := bufio.NewReader(r) 21 fmt.Print("Enter person ID number: ") 22 // An int32 field in the .proto file is represented as an int32 field 23 // in the generated Go struct. 24 if _, err := fmt.Fscanf(rd, "%d\n", &p.Id); err != nil { 25 return p, err 26 } 27 28 fmt.Print("Enter name: ") 29 name, err := rd.ReadString('\n') 30 if err != nil { 31 return p, err 32 } 33 // A string field in the .proto file results in a string field in Go. 34 // We trim the whitespace because rd.ReadString includes the trailing 35 // newline character in its output. 36 p.Name = strings.TrimSpace(name) 37 38 fmt.Print("Enter email address (blank for none): ") 39 email, err := rd.ReadString('\n') 40 if err != nil { 41 return p, err 42 } 43 p.Email = strings.TrimSpace(email) 44 45 for { 46 fmt.Print("Enter a phone number (or leave blank to finish): ") 47 phone, err := rd.ReadString('\n') 48 if err != nil { 49 return p, err 50 } 51 phone = strings.TrimSpace(phone) 52 if phone == "" { 53 break 54 } 55 // The PhoneNumber message type is nested within the Person 56 // message in the .proto file. This results in a Go struct 57 // named using the name of the parent prefixed to the name of 58 // the nested message. Just as with pb.Person, it can be 59 // created like any other struct. 60 pn := &pb.Person_PhoneNumber{ 61 Number: phone, 62 } 63 64 fmt.Print("Is this a mobile, home, or work phone? ") 65 ptype, err := rd.ReadString('\n') 66 if err != nil { 67 return p, err 68 } 69 ptype = strings.TrimSpace(ptype) 70 71 // A proto enum results in a Go constant for each enum value. 72 switch ptype { 73 case "mobile": 74 pn.Type = pb.Person_MOBILE 75 case "home": 76 pn.Type = pb.Person_HOME 77 case "work": 78 pn.Type = pb.Person_WORK 79 default: 80 fmt.Printf("Unknown phone type %q. Using default.\n", ptype) 81 } 82 83 // A repeated proto field maps to a slice field in Go. We can 84 // append to it like any other slice. 85 p.Phones = append(p.Phones, pn) 86 } 87 88 return p, nil 89 } 90 91 // Main reads the entire address book from a file, adds one person based on 92 // user input, then writes it back out to the same file. 93 func main() { 94 if len(os.Args) != 2 { 95 log.Fatalf("Usage: %s ADDRESS_BOOK_FILE\n", os.Args[0]) 96 } 97 fname := os.Args[1] 98 99 // Read the existing address book. 100 in, err := ioutil.ReadFile(fname) 101 if err != nil { 102 if os.IsNotExist(err) { 103 fmt.Printf("%s: File not found. Creating new file.\n", fname) 104 } else { 105 log.Fatalln("Error reading file:", err) 106 } 107 } 108 109 // [START marshal_proto] 110 book := &pb.AddressBook{} 111 // [START_EXCLUDE] 112 if err := proto.Unmarshal(in, book); err != nil { 113 log.Fatalln("Failed to parse address book:", err) 114 } 115 116 // Add an address. 117 addr, err := promptForAddress(os.Stdin) 118 if err != nil { 119 log.Fatalln("Error with address:", err) 120 } 121 book.People = append(book.People, addr) 122 // [END_EXCLUDE] 123 124 // Write the new address book back to disk. 125 out, err := proto.Marshal(book) 126 if err != nil { 127 log.Fatalln("Failed to encode address book:", err) 128 } 129 if err := ioutil.WriteFile(fname, out, 0644); err != nil { 130 log.Fatalln("Failed to write address book:", err) 131 } 132 // [END marshal_proto] 133 } 134