Skip to content

Commit

Permalink
Add a Go language example.
Browse files Browse the repository at this point in the history
This follows the other examples so that it can be used as a tutorial,
such as the ones at:
  https://developers.google.com/protocol-buffers/docs/tutorials

Even though Go generally does not use Makefiles, I added targets for the
Go examples to be consistent with the other languages.

Edit:

Fix Travis run. Change to use $HOME instead of ~. Add protoc to path.
GOPATH entry cannot start with shell metacharacter '~': "~/gocode"

Edit(2):

Fix Go code style to address comments.
  • Loading branch information
tswast committed Nov 25, 2015
1 parent f1e14fb commit 7e31c4d
Show file tree
Hide file tree
Showing 8 changed files with 410 additions and 0 deletions.
5 changes: 5 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ env:
- CONFIG=cpp
- CONFIG=cpp_distcheck
- CONFIG=csharp
- CONFIG=golang
- CONFIG=java_jdk6
- CONFIG=java_jdk7
- CONFIG=java_oracle7
Expand Down Expand Up @@ -48,6 +49,10 @@ matrix:
# which doesn't work on OS X.
- os: osx
env: CONFIG=csharp
# Requires installing golang, currently travis.sh is doing that with apt-get
# which doesn't work on OS X.
- os: osx
env: CONFIG=golang
# Add into the matrix OS X tests of Objective C (needs Xcode, so it won't
# work on other platforms). These are split so it doesn't take as long to run.
include:
Expand Down
21 changes: 21 additions & 0 deletions examples/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
all: cpp java python

cpp: add_person_cpp list_people_cpp
go: add_person_go list_people_go
gotest: add_person_gotest list_people_gotest
java: add_person_java list_people_java
python: add_person_python list_people_python

Expand All @@ -13,6 +15,8 @@ clean:
rm -f javac_middleman AddPerson*.class ListPeople*.class com/example/tutorial/*.class
rm -f protoc_middleman addressbook.pb.cc addressbook.pb.h addressbook_pb2.py com/example/tutorial/AddressBookProtos.java
rm -f *.pyc
rm -f protoc_middleman_go tutorial/*.pb.go add_person_go list_people_go
rmdir tutorial 2>/dev/null || true
rmdir com/example/tutorial 2>/dev/null || true
rmdir com/example 2>/dev/null || true
rmdir com 2>/dev/null || true
Expand All @@ -21,6 +25,11 @@ protoc_middleman: addressbook.proto
protoc --cpp_out=. --java_out=. --python_out=. addressbook.proto
@touch protoc_middleman

protoc_middleman_go: addressbook.proto
mkdir tutorial # make directory for go package
protoc --go_out=tutorial addressbook.proto
@touch protoc_middleman_go

add_person_cpp: add_person.cc protoc_middleman
pkg-config --cflags protobuf # fails if protobuf is not installed
c++ add_person.cc addressbook.pb.cc -o add_person_cpp `pkg-config --cflags --libs protobuf`
Expand All @@ -29,6 +38,18 @@ list_people_cpp: list_people.cc protoc_middleman
pkg-config --cflags protobuf # fails if protobuf is not installed
c++ list_people.cc addressbook.pb.cc -o list_people_cpp `pkg-config --cflags --libs protobuf`

add_person_go: add_person.go protoc_middleman_go
go build -o add_person_go add_person.go

add_person_gotest: add_person_test.go add_person_go
go test add_person.go add_person_test.go

list_people_go: list_people.go protoc_middleman_go
go build -o list_people_go list_people.go

list_people_gotest: list_people.go list_people_go
go test list_people.go list_people_test.go

javac_middleman: AddPerson.java ListPeople.java protoc_middleman
javac AddPerson.java ListPeople.java com/example/tutorial/AddressBookProtos.java
@touch javac_middleman
Expand Down
25 changes: 25 additions & 0 deletions examples/README.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,28 @@ These examples are part of the Protocol Buffers tutorial, located at:
* Note that on some platforms you may have to edit the Makefile and remove
"-lpthread" from the linker commands (perhaps replacing it with something else).
We didn't do this automatically because we wanted to keep the example simple.

## Go ##

The Go example requires a plugin to the protocol buffer compiler, so it is not
build with all the other examples. See:
https://github.com/golang/protobuf
for more information about Go protocol buffer support.

First, install the the Protocol Buffers compiler (protoc).
Then, install the Go Protocol Buffers plugin
($GOPATH/bin must be in your $PATH for protoc to find it):
go get github.com/golang/protobuf/protoc-gen-go

Build the Go samples in this directory with "make go". This creates the
following executable files in the current directory:
add_person_go list_people_go
To run the example:
./add_person_go addressbook.data
to add a person to the protocol buffer encoded file addressbook.data. The file
is created if it does not exist. To view the data, run:
./list_people_go addressbook.data

Observe that the C++, Python, and Java examples in this directory run in a
similar way and can view/modify files created by the Go example and vice
versa.
128 changes: 128 additions & 0 deletions examples/add_person.go
Original file line number Diff line number Diff line change
@@ -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)
}
}
58 changes: 58 additions & 0 deletions examples/add_person_test.go
Original file line number Diff line number Diff line change
@@ -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])
}

}
}
59 changes: 59 additions & 0 deletions examples/list_people.go
Original file line number Diff line number Diff line change
@@ -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)
}
Loading

0 comments on commit 7e31c4d

Please sign in to comment.