1package main
2
3import (
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/protocolbuffers/protobuf/examples/tutorial"
14)
15
16func 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.
93func 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