You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
133 lines
3.3 KiB
133 lines
3.3 KiB
package main |
|
|
|
import ( |
|
"bufio" |
|
"fmt" |
|
"io" |
|
"io/ioutil" |
|
"log" |
|
"os" |
|
"strings" |
|
|
|
"github.com/golang/protobuf/proto" |
|
pb "github.com/protocolbuffers/protobuf/examples/tutorial" |
|
) |
|
|
|
func promptForAddress(r io.Reader) (*pb.Person, error) { |
|
// A protocol buffer can be created like any struct. |
|
p := &pb.Person{} |
|
|
|
rd := bufio.NewReader(r) |
|
fmt.Print("Enter person ID number: ") |
|
// An int32 field in the .proto file is represented as an int32 field |
|
// in the generated Go struct. |
|
if _, err := fmt.Fscanf(rd, "%d\n", &p.Id); err != nil { |
|
return p, err |
|
} |
|
|
|
fmt.Print("Enter name: ") |
|
name, err := rd.ReadString('\n') |
|
if err != nil { |
|
return p, err |
|
} |
|
// A string field in the .proto file results in a string field in Go. |
|
// We trim the whitespace because rd.ReadString includes the trailing |
|
// newline character in its output. |
|
p.Name = strings.TrimSpace(name) |
|
|
|
fmt.Print("Enter email address (blank for none): ") |
|
email, err := rd.ReadString('\n') |
|
if err != nil { |
|
return p, err |
|
} |
|
p.Email = strings.TrimSpace(email) |
|
|
|
for { |
|
fmt.Print("Enter a phone number (or leave blank to finish): ") |
|
phone, err := rd.ReadString('\n') |
|
if err != nil { |
|
return p, err |
|
} |
|
phone = strings.TrimSpace(phone) |
|
if phone == "" { |
|
break |
|
} |
|
// The PhoneNumber message type is nested within the Person |
|
// message in the .proto file. This results in a Go struct |
|
// named using the name of the parent prefixed to the name of |
|
// the nested message. Just as with pb.Person, it can be |
|
// created like any other struct. |
|
pn := &pb.Person_PhoneNumber{ |
|
Number: phone, |
|
} |
|
|
|
fmt.Print("Is this a mobile, home, or work phone? ") |
|
ptype, err := rd.ReadString('\n') |
|
if err != nil { |
|
return p, err |
|
} |
|
ptype = strings.TrimSpace(ptype) |
|
|
|
// A proto enum results in a Go constant for each enum value. |
|
switch ptype { |
|
case "mobile": |
|
pn.Type = pb.Person_MOBILE |
|
case "home": |
|
pn.Type = pb.Person_HOME |
|
case "work": |
|
pn.Type = pb.Person_WORK |
|
default: |
|
fmt.Printf("Unknown phone type %q. Using default.\n", ptype) |
|
} |
|
|
|
// A repeated proto field maps to a slice field in Go. We can |
|
// append to it like any other slice. |
|
p.Phones = append(p.Phones, pn) |
|
} |
|
|
|
return p, nil |
|
} |
|
|
|
// Main reads the entire address book from a file, adds one person based on |
|
// user input, then writes it back out to the same file. |
|
func main() { |
|
if len(os.Args) != 2 { |
|
log.Fatalf("Usage: %s ADDRESS_BOOK_FILE\n", os.Args[0]) |
|
} |
|
fname := os.Args[1] |
|
|
|
// Read the existing address book. |
|
in, err := ioutil.ReadFile(fname) |
|
if err != nil { |
|
if os.IsNotExist(err) { |
|
fmt.Printf("%s: File not found. Creating new file.\n", fname) |
|
} else { |
|
log.Fatalln("Error reading file:", err) |
|
} |
|
} |
|
|
|
// [START marshal_proto] |
|
book := &pb.AddressBook{} |
|
// [START_EXCLUDE] |
|
if err := proto.Unmarshal(in, book); err != nil { |
|
log.Fatalln("Failed to parse address book:", err) |
|
} |
|
|
|
// Add an address. |
|
addr, err := promptForAddress(os.Stdin) |
|
if err != nil { |
|
log.Fatalln("Error with address:", err) |
|
} |
|
book.People = append(book.People, addr) |
|
// [END_EXCLUDE] |
|
|
|
// Write the new address book back to disk. |
|
out, err := proto.Marshal(book) |
|
if err != nil { |
|
log.Fatalln("Failed to encode address book:", err) |
|
} |
|
if err := ioutil.WriteFile(fname, out, 0644); err != nil { |
|
log.Fatalln("Failed to write address book:", err) |
|
} |
|
// [END marshal_proto] |
|
}
|
|
|