Add a Go language example.
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.
This commit is contained in:
parent
f1e14fba23
commit
7e31c4d930
@ -15,6 +15,7 @@ env:
|
||||
- CONFIG=cpp
|
||||
- CONFIG=cpp_distcheck
|
||||
- CONFIG=csharp
|
||||
- CONFIG=golang
|
||||
- CONFIG=java_jdk6
|
||||
- CONFIG=java_jdk7
|
||||
- CONFIG=java_oracle7
|
||||
@ -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:
|
||||
|
@ -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
|
||||
|
||||
@ -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
|
||||
@ -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`
|
||||
@ -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
|
||||
|
@ -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
examples/add_person.go
Normal file
128
examples/add_person.go
Normal file
@ -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
examples/add_person_test.go
Normal file
58
examples/add_person_test.go
Normal file
@ -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
examples/list_people.go
Normal file
59
examples/list_people.go
Normal file
@ -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)
|
||||
}
|
96
examples/list_people_test.go
Normal file
96
examples/list_people_test.go
Normal file
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
18
travis.sh
18
travis.sh
@ -47,6 +47,24 @@ build_csharp() {
|
||||
cd conformance && make test_csharp && cd ..
|
||||
}
|
||||
|
||||
build_golang() {
|
||||
# Go build needs `protoc`.
|
||||
internal_build_cpp
|
||||
# Add protoc to the path so that the examples build finds it.
|
||||
export PATH="`pwd`/src:$PATH"
|
||||
|
||||
# Install Go and the Go protobuf compiler plugin.
|
||||
sudo apt-get update -qq
|
||||
sudo apt-get install -qq golang
|
||||
export GOPATH="$HOME/gocode"
|
||||
mkdir -p "$GOPATH/src/github.com/google"
|
||||
ln -s "`pwd`" "$GOPATH/src/github.com/google/protobuf"
|
||||
export PATH="$GOPATH/bin:$PATH"
|
||||
go get github.com/golang/protobuf/protoc-gen-go
|
||||
|
||||
cd examples && make gotest && cd ..
|
||||
}
|
||||
|
||||
use_java() {
|
||||
version=$1
|
||||
case "$version" in
|
||||
|
Loading…
Reference in New Issue
Block a user