Implement protobuf package name extractor
This commit is contained in:
Родитель
854f30c888
Коммит
47eaf4bb8f
|
@ -301,6 +301,9 @@ func (self *SvcScanner) FastForward() error {
|
||||||
return nil
|
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) {
|
func (self *SvcScanner) ReadUnit() ([]rune, error) {
|
||||||
var rv []rune
|
var rv []rune
|
||||||
var err error = nil
|
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)
|
||||||
|
}
|
||||||
|
}
|
Загрузка…
Ссылка в новой задаче