commit
1a59a715dc
8 changed files with 410 additions and 0 deletions
@ -0,0 +1,128 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bufio" |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
"strings" |
||||
|
||||
"github.com/golang/protobuf/proto" |
||||
pb "github.com/google/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) |
||||
} |
||||
} |
||||
book := &pb.AddressBook{} |
||||
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) |
||||
|
||||
// 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) |
||||
} |
||||
} |
@ -0,0 +1,58 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"strings" |
||||
"testing" |
||||
|
||||
"github.com/golang/protobuf/proto" |
||||
pb "github.com/google/protobuf/examples/tutorial" |
||||
) |
||||
|
||||
func TestPromptForAddressReturnsAddress(t *testing.T) { |
||||
in := `12345 |
||||
Example Name |
||||
name@example.com |
||||
123-456-7890 |
||||
home |
||||
222-222-2222 |
||||
mobile |
||||
111-111-1111 |
||||
work |
||||
777-777-7777 |
||||
unknown |
||||
|
||||
` |
||||
got, err := promptForAddress(strings.NewReader(in)) |
||||
if err != nil { |
||||
t.Fatalf("promptForAddress(%q) had unexpected error: %s", in, err.Error()) |
||||
} |
||||
if got.Id != 12345 { |
||||
t.Errorf("promptForAddress(%q) got %d, want ID %d", in, got.Id, 12345) |
||||
} |
||||
if got.Name != "Example Name" { |
||||
t.Errorf("promptForAddress(%q) => want name %q, got %q", "Example Name", got.Name) |
||||
} |
||||
if got.Email != "name@example.com" { |
||||
t.Errorf("promptForAddress(%q) => want email %q, got %q", "name@example.com", got.Email) |
||||
} |
||||
|
||||
want := []*pb.Person_PhoneNumber{ |
||||
{Number: "123-456-7890", Type: pb.Person_HOME}, |
||||
{Number: "222-222-2222", Type: pb.Person_MOBILE}, |
||||
{Number: "111-111-1111", Type: pb.Person_WORK}, |
||||
{Number: "777-777-7777", Type: pb.Person_MOBILE}, |
||||
} |
||||
if len(got.Phones) != len(want) { |
||||
t.Errorf("want %d phone numbers, got %d", len(want), len(got.Phones)) |
||||
} |
||||
phones := len(got.Phones) |
||||
if phones > len(want) { |
||||
phones = len(want) |
||||
} |
||||
for i := 0; i < phones; i++ { |
||||
if !proto.Equal(got.Phones[i], want[i]) { |
||||
t.Errorf("want phone %q, got %q", *want[i], *got.Phones[i]) |
||||
} |
||||
|
||||
} |
||||
} |
@ -0,0 +1,59 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"io" |
||||
"io/ioutil" |
||||
"log" |
||||
"os" |
||||
|
||||
"github.com/golang/protobuf/proto" |
||||
pb "github.com/google/protobuf/examples/tutorial" |
||||
) |
||||
|
||||
func listPeople(w io.Writer, book *pb.AddressBook) { |
||||
for _, p := range book.People { |
||||
fmt.Fprintln(w, "Person ID:", p.Id) |
||||
fmt.Fprintln(w, " Name:", p.Name) |
||||
if p.Email != "" { |
||||
fmt.Fprintln(w, " E-mail address:", p.Email) |
||||
} |
||||
|
||||
for _, pn := range p.Phones { |
||||
switch pn.Type { |
||||
case pb.Person_MOBILE: |
||||
fmt.Fprint(w, " Mobile phone #: ") |
||||
case pb.Person_HOME: |
||||
fmt.Fprint(w, " Home phone #: ") |
||||
case pb.Person_WORK: |
||||
fmt.Fprint(w, " Work phone #: ") |
||||
} |
||||
fmt.Fprintln(w, pn.Number) |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Main reads the entire address book from a file and prints all the
|
||||
// information inside.
|
||||
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) |
||||
} |
||||
} |
||||
book := &pb.AddressBook{} |
||||
if err := proto.Unmarshal(in, book); err != nil { |
||||
log.Fatalln("Failed to parse address book:", err) |
||||
} |
||||
|
||||
listPeople(os.Stdout, book) |
||||
} |
@ -0,0 +1,96 @@ |
||||
package main |
||||
|
||||
import ( |
||||
"bytes" |
||||
"strings" |
||||
"testing" |
||||
|
||||
pb "github.com/google/protobuf/examples/tutorial" |
||||
) |
||||
|
||||
func TestListPeopleWritesList(t *testing.T) { |
||||
buf := new(bytes.Buffer) |
||||
in := pb.AddressBook{[]*pb.Person{ |
||||
{ |
||||
Name: "John Doe", |
||||
Id: 101, |
||||
Email: "john@example.com", |
||||
}, |
||||
{ |
||||
Name: "Jane Doe", |
||||
Id: 102, |
||||
}, |
||||
{ |
||||
Name: "Jack Doe", |
||||
Id: 201, |
||||
Email: "jack@example.com", |
||||
Phones: []*pb.Person_PhoneNumber{ |
||||
{Number: "555-555-5555", Type: pb.Person_WORK}, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "Jack Buck", |
||||
Id: 301, |
||||
Email: "buck@example.com", |
||||
Phones: []*pb.Person_PhoneNumber{ |
||||
{Number: "555-555-0000", Type: pb.Person_HOME}, |
||||
{Number: "555-555-0001", Type: pb.Person_MOBILE}, |
||||
{Number: "555-555-0002", Type: pb.Person_WORK}, |
||||
}, |
||||
}, |
||||
{ |
||||
Name: "Janet Doe", |
||||
Id: 1001, |
||||
Email: "janet@example.com", |
||||
Phones: []*pb.Person_PhoneNumber{ |
||||
{Number: "555-777-0000"}, |
||||
{Number: "555-777-0001", Type: pb.Person_HOME}, |
||||
}, |
||||
}, |
||||
}} |
||||
listPeople(buf, &in) |
||||
want := strings.Split(`Person ID: 101 |
||||
Name: John Doe |
||||
E-mail address: john@example.com |
||||
Person ID: 102 |
||||
Name: Jane Doe |
||||
Person ID: 201 |
||||
Name: Jack Doe |
||||
E-mail address: jack@example.com |
||||
Work phone #: 555-555-5555 |
||||
Person ID: 301 |
||||
Name: Jack Buck |
||||
E-mail address: buck@example.com |
||||
Home phone #: 555-555-0000 |
||||
Mobile phone #: 555-555-0001 |
||||
Work phone #: 555-555-0002 |
||||
Person ID: 1001 |
||||
Name: Janet Doe |
||||
E-mail address: janet@example.com |
||||
Mobile phone #: 555-777-0000 |
||||
Home phone #: 555-777-0001 |
||||
`, "\n") |
||||
got := strings.Split(buf.String(), "\n") |
||||
if len(got) != len(want) { |
||||
t.Errorf( |
||||
"listPeople(%s) =>\n\t%q has %d lines, want %d", |
||||
in.String(), |
||||
buf.String(), |
||||
len(got), |
||||
len(want)) |
||||
} |
||||
lines := len(got) |
||||
if lines > len(want) { |
||||
lines = len(want) |
||||
} |
||||
for i := 0; i < lines; i++ { |
||||
if got[i] != want[i] { |
||||
t.Errorf( |
||||
"listPeople(%s) =>\n\tline %d %q, want %q", |
||||
in.String(), |
||||
i, |
||||
got[i], |
||||
want[i]) |
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue