Implement protobuf package name extractor

This commit is contained in:
Leland Batey 2016-10-22 10:47:05 -07:00
Родитель 854f30c888
Коммит 47eaf4bb8f
3 изменённых файлов: 203 добавлений и 0 удалений

Просмотреть файл

@ -301,6 +301,9 @@ func (self *SvcScanner) FastForward() error {
return nil
}
// ReadUnit returns the next "group" of runes found in the input stream. If the
// end of the stream is reached, io.EOF will be returned as error. No other
// errors will be returned.
func (self *SvcScanner) ReadUnit() ([]rune, error) {
var rv []rune
var err error = nil

Просмотреть файл

@ -0,0 +1,111 @@
/*
Package parsepkgname provides functions for extracting the name of a package
from a protocol buffer definition file. For example, given a protocol buffer 3
file like this:
// A comment about this proto file
package examplepackage;
// and the rest of the file goes here
The functions in this package would extract the name "examplepackage" as the
name of the protobuf package.
*/
package parsepkgname
import (
"io"
"unicode"
"github.com/TuneLab/go-truss/deftree/svcparse"
)
type Token int
const (
IDENT Token = iota
WHITESPACE
COMMENT
OTHER
)
type Scanner interface {
// ReadUnit must return groups of runes representing at least the following
// lexical groups:
//
// ident
// comments (c++ style single line comments and block comments)
// whitespace
//
// If you need a scanner which provides these out of the box, see the
// SvcScanner struct in github.com/TuneLab/go-truss/deftree/svcparse
ReadUnit() ([]rune, error)
}
func categorize(unit []rune) Token {
rv := OTHER
r := unit[0]
switch {
case unicode.IsLetter(r):
rv = IDENT
case unicode.IsDigit(r):
rv = IDENT
case r == '_':
rv = IDENT
case unicode.IsSpace(r):
rv = WHITESPACE
case r == '/' && len(unit) > 1:
rv = COMMENT
}
return rv
}
// PackageNameFromFile accepts an io.Reader, the contents of which should be a
// valid proto3 file, and returns the name of the protobuf package for that
// file.
func PackageNameFromFile(protofile io.Reader) (string, error) {
scanner := svcparse.NewSvcScanner(protofile)
return GetPackageName(scanner)
}
// GetPackageName accepts a Scanner for a protobuf file and returns the name of
// the protobuf package that the file lives within.
func GetPackageName(scanner Scanner) (string, error) {
foundpackage := false
// A nice way to ignore comments. Recursively calls itself until it
// recieves a unit from the scanner which is not a comment.
var readIgnoreComment func(Scanner) (Token, []rune, error)
readIgnoreComment = func(scn Scanner) (Token, []rune, error) {
unit, err := scanner.ReadUnit()
if err != nil {
return OTHER, nil, err
}
tkn := categorize(unit)
if tkn == COMMENT {
return readIgnoreComment(scn)
}
return tkn, unit, err
}
for {
tkn, unit, err := readIgnoreComment(scanner)
// Err may only be io.EOF
if err != nil {
return "", err
}
if foundpackage {
if tkn == IDENT {
return string(unit), nil
} else if tkn == WHITESPACE {
continue
} else {
foundpackage = false
}
} else {
if tkn == IDENT && string(unit) == "package" {
foundpackage = true
}
}
}
}

Просмотреть файл

@ -0,0 +1,89 @@
package parsepkgname
import (
"io"
"strings"
"testing"
)
type testScanner struct {
contents [][]rune
position int
}
func (t *testScanner) ReadUnit() ([]rune, error) {
if t.position < len(t.contents) {
rv := t.contents[t.position]
t.position += 1
return rv, nil
} else {
return nil, io.EOF
}
}
func NewTestScanner(units []string) *testScanner {
rv := testScanner{position: 0}
for _, u := range units {
rv.contents = append(rv.contents, []rune(u))
}
return &rv
}
func TestGetPackageName_simple(t *testing.T) {
basicContents := []string{
"\n",
"package",
" ",
"examplename",
";",
"\n",
}
scn := NewTestScanner(basicContents)
want := "examplename"
got, err := GetPackageName(scn)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}
func TestGetPackageName_mid_comment(t *testing.T) {
contents := []string{
"\n",
"package",
" ",
"/* comment in the middle of the declaration */",
"examplename",
";",
"\n",
}
scn := NewTestScanner(contents)
want := "examplename"
got, err := GetPackageName(scn)
if err != nil {
t.Fatal(err)
}
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}
func TestPackageNameFromFile(t *testing.T) {
code := `
// A comment about this proto file
package /* some mid-definition comment */ examplepackage;
// and the rest of the file goes here
`
name, err := PackageNameFromFile(strings.NewReader(code))
if err != nil {
t.Fatal(err)
}
got := name
want := "examplepackage"
if got != want {
t.Fatalf("Got %q for package name, want %q", got, want)
}
}