pax_global_header00006660000000000000000000000064147307750210014520gustar00rootroot0000000000000052 comment=65fb5f151e8ab32d91eaa29836d62faf6878d83b golang-github-emicklei-proto-1.14.0/000077500000000000000000000000001473077502100172535ustar00rootroot00000000000000golang-github-emicklei-proto-1.14.0/.github/000077500000000000000000000000001473077502100206135ustar00rootroot00000000000000golang-github-emicklei-proto-1.14.0/.github/FUNDING.yml000066400000000000000000000000201473077502100224200ustar00rootroot00000000000000github: emickleigolang-github-emicklei-proto-1.14.0/.github/workflows/000077500000000000000000000000001473077502100226505ustar00rootroot00000000000000golang-github-emicklei-proto-1.14.0/.github/workflows/go.yml000066400000000000000000000012611473077502100240000ustar00rootroot00000000000000# This workflow will test a golang project # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-go name: Go on: push: branches: [ "master" ] pull_request: branches: - '*' # matches every branch that doesn't contain a '/' - '*/*' # matches every branch containing a single '/' - '**' # matches every branch - '!master' # excludes master jobs: build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - name: Set up Go uses: actions/setup-go@v4 with: go-version: '1.23' - name: Test run: go test -v -cover ./...golang-github-emicklei-proto-1.14.0/.gitignore000066400000000000000000000001011473077502100212330ustar00rootroot00000000000000/.idea/ /.tmp/ /.vscode/ /bin/ debug.test .DS_Store coverage.txt golang-github-emicklei-proto-1.14.0/.travis.yml000066400000000000000000000001471473077502100213660ustar00rootroot00000000000000language: go go: - 1.12.x script: - make after_success: - bash <(curl -s https://codecov.io/bash)golang-github-emicklei-proto-1.14.0/CHANGES.md000066400000000000000000000030501473077502100206430ustar00rootroot00000000000000## v1.14.0 (2024-12-18) - parse edition element (PR #147, ISSUE #145) ## v1.13.4 (2024-12-17) - fixed handling identifiers known as numbers by scanner (PR #146) ## v1.13.3 (2024-12-04) - fixed inline comment in option (#143) ## v1.13.2 (2024-01-24) - allow keyword as field name (such as message,service, etc) ## v1.13.1 (2024-01-24) - allow embedded comment in between normal field parts (#131) ## v1.13.0 (2023-12-09) - walk options in Enum fields (#140) ## v1.12.2 (2023-11-02) - allow comments in array of literals of option (#138) - adds Comment field in Literal ## v1.12.1 (2023-07-18) - add IsDeprecated on EnumField ## v1.12.0 (2023-07-14) - add IsDeprecated on Field ## v1.11.2 (2023-05-01) - fix Parse failure on negative reserved enums (#133) ## v1.11.1 (2022-12-01) - added Doc for MapField so it implements Documented ## v1.11.0 - added WithNormalField handler ## v1.10.0 - added NoopVisitor and updated README with an example ## v1.9.2 - fix for scanning content of single-quote option values (#129) ## v1.9.1 - fix for issue #127 reserved keyword as suffix in type (#128) ## v1.9.0 - Fix & guard Parent value for options (#124) ## v1.8.0 - Add WithImport handler. ## v1.7.0 - Add WithPackage handler for walking a proto. ## v1.6.17 - add Oneof documented ## v1.6.16 - Handle inline comments before definition body ## v1.6.15 - Handle scanner change in Go 1.13 ## v1.6.14 - Handle comment inside option array value ## v1.6.13 - fixes breaking change introduced by v1.6.11 w.r.t Literal ## < v1.6.12 - see git loggolang-github-emicklei-proto-1.14.0/LICENSE000066400000000000000000000020561473077502100202630ustar00rootroot00000000000000Copyright (c) 2017 Ernest Micklei MIT License Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.golang-github-emicklei-proto-1.14.0/Makefile000066400000000000000000000035571473077502100207250ustar00rootroot00000000000000SHELL := /bin/bash -o pipefail UNAME_OS := $(shell uname -s) UNAME_ARCH := $(shell uname -m) TMP_BASE := .tmp TMP := $(TMP_BASE)/$(UNAME_OS)/$(UNAME_ARCH) TMP_BIN = $(TMP)/bin GOLINT_VERSION := 8f45f776aaf18cebc8d65861cc70c33c60471952 GOLINT := $(TMP_BIN)/golint $(GOLINT): $(eval GOLINT_TMP := $(shell mktemp -d)) @cd $(GOLINT_TMP); go get github.com/golang/lint/golint@$(GOLINT_VERSION) @rm -rf $(GOLINT_TMP) ERRCHECK_VERSION := v1.2.0 ERRCHECK := $(TMP_BIN)/errcheck $(ERRCHECK): $(eval ERRCHECK_TMP := $(shell mktemp -d)) @cd $(ERRCHECK_TMP); go get github.com/kisielk/errcheck@$(ERRCHECK_VERSION) @rm -rf $(ERRCHECK_TMP) STATICCHECK_VERSION := c2f93a96b099cbbec1de36336ab049ffa620e6d7 STATICCHECK := $(TMP_BIN)/staticcheck $(STATICCHECK): $(eval STATICCHECK_TMP := $(shell mktemp -d)) @cd $(STATICCHECK_TMP); go get honnef.co/go/tools/cmd/staticcheck@$(STATICCHECK_VERSION) @rm -rf $(STATICCHECK_TMP) unexport GOPATH export GO111MODULE := on export GOBIN := $(abspath $(TMP_BIN)) export PATH := $(GOBIN):$(PATH) .DEFAULT_GOAL := all .PHONY: all all: lint test .PHONY: install install: go install ./... .PHONY: golint golint: $(GOLINT) @# TODO: readd cmd/proto2gql when fixed @#for file in $(shell find . -name '*.go'); do for file in $(shell find . -name '*.go' | grep -v cmd/proto2gql); do \ golint $${file}; \ if [ -n "$$(golint $${file})" ]; then \ exit 1; \ fi; \ done .PHONY: vet vet: go vet ./... .PHONY: testdeps errcheck: $(ERRCHECK) errcheck ./... .PHONY: staticcheck staticcheck: $(STATICCHECK) staticcheck -checks "all -U1000" ./... .PHONY: lint # TODO: readd errcheck when fixed #lint: golint vet errcheck staticcheck #lint: golint vet staticcheck lint: golint vet .PHONY: test test: go test -race -coverprofile=coverage.txt -covermode=atomic ./... .PHONY: clean clean: go clean -i ./... .PHONY: integration integration: PB=y go test -cover golang-github-emicklei-proto-1.14.0/README.md000066400000000000000000000041401473077502100205310ustar00rootroot00000000000000# proto [![Go](https://github.com/emicklei/proto/actions/workflows/go.yml/badge.svg)](https://github.com/emicklei/proto/actions/workflows/go.yml) [![Go Report Card](https://goreportcard.com/badge/github.com/emicklei/proto)](https://goreportcard.com/report/github.com/emicklei/proto) [![GoDoc](https://pkg.go.dev/badge/github.com/emicklei/proto)](https://pkg.go.dev/github.com/emicklei/proto) [![codecov](https://codecov.io/gh/emicklei/proto/branch/master/graph/badge.svg)](https://codecov.io/gh/emicklei/proto) Package in Go for parsing Google Protocol Buffers [.proto files version 2 + 3](https://developers.google.com/protocol-buffers/docs/reference/proto3-spec) ### install go get -u -v github.com/emicklei/proto ### usage package main import ( "fmt" "os" "github.com/emicklei/proto" ) func main() { reader, _ := os.Open("test.proto") defer reader.Close() parser := proto.NewParser(reader) definition, _ := parser.Parse() proto.Walk(definition, proto.WithService(handleService), proto.WithMessage(handleMessage)) } func handleService(s *proto.Service) { fmt.Println(s.Name) } func handleMessage(m *proto.Message) { lister := new(optionLister) for _, each := range m.Elements { each.Accept(lister) } fmt.Println(m.Name) } type optionLister struct { proto.NoopVisitor } func (l optionLister) VisitOption(o *proto.Option) { fmt.Println(o.Name) } ### validation Current parser implementation is not completely validating `.proto` definitions. In many but not all cases, the parser will report syntax errors when reading unexpected charaters or tokens. Use some linting tools (e.g. https://github.com/uber/prototool) or `protoc` for full validation. ### contributions See [proto-contrib](https://github.com/emicklei/proto-contrib) for other contributions on top of this package such as protofmt, proto2xsd and proto2gql. [protobuf2map](https://github.com/emicklei/protobuf2map) is a small package for inspecting serialized protobuf messages using its `.proto` definition. © 2017-2022, [ernestmicklei.com](http://ernestmicklei.com). MIT License. Contributions welcome. golang-github-emicklei-proto-1.14.0/comment.go000066400000000000000000000117671473077502100212600ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "strings" "text/scanner" ) // Comment one or more comment text lines, either in c- or c++ style. type Comment struct { Position scanner.Position // Lines are comment text lines without prefixes //, ///, /* or suffix */ Lines []string Cstyle bool // refers to /* ... */, C++ style is using // ExtraSlash bool // is true if the comment starts with 3 slashes } // newComment returns a comment. func newComment(pos scanner.Position, lit string) *Comment { extraSlash := strings.HasPrefix(lit, "///") isCstyle := strings.HasPrefix(lit, "/*") && strings.HasSuffix(lit, "*/") var lines []string if isCstyle { withoutMarkers := strings.TrimRight(strings.TrimLeft(lit, "/*"), "*/") lines = strings.Split(withoutMarkers, "\n") } else { lines = strings.Split(strings.TrimLeft(lit, "/"), "\n") } return &Comment{Position: pos, Lines: lines, Cstyle: isCstyle, ExtraSlash: extraSlash} } type inlineComment struct { line string extraSlash bool } // Accept dispatches the call to the visitor. func (c *Comment) Accept(v Visitor) { v.VisitComment(c) } // Merge appends all lines from the argument comment. func (c *Comment) Merge(other *Comment) { c.Lines = append(c.Lines, other.Lines...) c.Cstyle = c.Cstyle || other.Cstyle } func (c Comment) hasTextOnLine(line int) bool { if len(c.Lines) == 0 { return false } return c.Position.Line <= line && line <= c.Position.Line+len(c.Lines)-1 } // Message returns the first line or empty if no lines. func (c Comment) Message() string { if len(c.Lines) == 0 { return "" } return c.Lines[0] } // commentInliner is for types that can have an inline comment. type commentInliner interface { inlineComment(c *Comment) } // maybeScanInlineComment tries to scan comment on the current line ; if present then set it for the last element added. func maybeScanInlineComment(p *Parser, c elementContainer) { currentPos := p.scanner.Position // see if there is an inline Comment pos, tok, lit := p.next() esize := len(c.elements()) // seen comment and on same line and elements have been added if tCOMMENT == tok && pos.Line == currentPos.Line && esize > 0 { // if the last added element can have an inline comment then set it last := c.elements()[esize-1] if inliner, ok := last.(commentInliner); ok { // TODO skip multiline? inliner.inlineComment(newComment(pos, lit)) } } else { p.nextPut(pos, tok, lit) } } // takeLastCommentIfEndsOnLine removes and returns the last element of the list if it is a Comment func takeLastCommentIfEndsOnLine(list []Visitee, line int) (*Comment, []Visitee) { if len(list) == 0 { return nil, list } if last, ok := list[len(list)-1].(*Comment); ok && last.hasTextOnLine(line) { return last, list[:len(list)-1] } return nil, list } // mergeOrReturnComment creates a new comment and tries to merge it with the last element (if is a comment and is on the next line). func mergeOrReturnComment(elements []Visitee, lit string, pos scanner.Position) *Comment { com := newComment(pos, lit) esize := len(elements) if esize == 0 { return com } // last element must be a comment to merge last, ok := elements[esize-1].(*Comment) if !ok { return com } // do not merge c-style comments if last.Cstyle { return com } // last comment has text on previous line // TODO handle last line of file could be inline comment if !last.hasTextOnLine(pos.Line - 1) { return com } last.Merge(com) return nil } // parent is part of elementContainer func (c *Comment) parent(Visitee) {} // consumeCommentFor is for reading and taking all comment lines before the body of an element (starting at {) func consumeCommentFor(p *Parser, e elementContainer) { pos, tok, lit := p.next() if tok == tCOMMENT { if com := mergeOrReturnComment(e.elements(), lit, pos); com != nil { // not merged? e.addElement(com) } consumeCommentFor(p, e) // bit of recursion is fine } else { p.nextPut(pos, tok, lit) } } golang-github-emicklei-proto-1.14.0/comment_test.go000066400000000000000000000224421473077502100223070ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "testing" "text/scanner" ) var startPosition = scanner.Position{Line: 1, Column: 1} func TestCreateComment(t *testing.T) { c0 := newComment(startPosition, "") if got, want := len(c0.Lines), 1; got != want { t.Errorf("got [%v] want [%v]", got, want) } c1 := newComment(startPosition, `hello world`) if got, want := len(c1.Lines), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := c1.Lines[0], "hello"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := c1.Lines[1], "world"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := c1.Cstyle, false; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestTakeLastComment(t *testing.T) { c0 := newComment(startPosition, "hi") c1 := newComment(startPosition, "there") _, l := takeLastCommentIfEndsOnLine([]Visitee{c0, c1}, 1) if got, want := len(l), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := l[0], c0; got != want { t.Errorf("got [%v] want [%v]", c1, want) } } func TestParseCommentWithEmptyLinesIndentAndTripleSlash(t *testing.T) { proto := ` // comment 1 // comment 2 // // comment 3 /// comment 4` p := newParserOn(proto) def, err := p.Parse() if err != nil { t.Fatal(err) } //spew.Dump(def) if got, want := len(def.Elements), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := len(def.Elements[0].(*Comment).Lines), 5; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Lines[4], " comment 4"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Position.Line, 2; got != want { t.Fatalf("got [%d] want [%d]", got, want) } if got, want := def.Elements[0].(*Comment).Cstyle, false; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestParseCStyleComment(t *testing.T) { proto := ` /*comment 1 comment 2 comment 3 comment 4 */` p := newParserOn(proto) def, err := p.Parse() if err != nil { t.Fatal(err) } if got, want := len(def.Elements), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := len(def.Elements[0].(*Comment).Lines), 6; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Lines[3], "comment 3"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Lines[4], " comment 4"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Cstyle, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestParseCStyleCommentWithIndent(t *testing.T) { proto := ` /*comment 1 comment 2 comment 3 comment 4 */` p := newParserOn(proto) def, err := p.Parse() if err != nil { t.Fatal(err) } if got, want := len(def.Elements), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := len(def.Elements[0].(*Comment).Lines), 6; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Lines[0], "comment 1"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Lines[3], "\tcomment 3"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Lines[4], "\t comment 4"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Cstyle, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestParseCStyleOneLineComment(t *testing.T) { proto := `/* comment 1 */` p := newParserOn(proto) def, err := p.Parse() if err != nil { t.Fatal(err) } if got, want := len(def.Elements), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := len(def.Elements[0].(*Comment).Lines), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Lines[0], " comment 1 "; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Cstyle, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestParseCStyleInlineComment(t *testing.T) { proto := `message Foo { int64 hello = 1; /* comment 1 */ }` p := newParserOn(proto) def := new(Proto) err := def.parse(p) if err != nil { t.Fatal(err) } m := def.Elements[0].(*Message) if len(m.Elements) != 1 { t.Fatal("expected one element", m.Elements) } f := m.Elements[0].(*NormalField) comment := f.InlineComment if comment == nil { t.Fatal("no inline comment") } if got, want := len(comment.Lines), 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := comment.Lines[0], ""; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := comment.Lines[1], " comment 1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := comment.Cstyle, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestParseCommentWithTripleSlash(t *testing.T) { proto := ` /// comment 1 ` p := newParserOn(proto) def, err := p.Parse() if err != nil { t.Fatal(err) } if got, want := len(def.Elements), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).ExtraSlash, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Lines[0], " comment 1"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[0].(*Comment).Position.Line, 2; got != want { t.Fatalf("got [%d] want [%d]", got, want) } } func TestCommentAssociation(t *testing.T) { src := ` // foo1 // foo2 // bar syntax = "proto3"; // baz // bat1 // bat2 package bat; // Oneway is the return type to use for an rpc method if // the method should be generated as oneway. message Oneway { bool ack = 1; }` p := newParserOn(src) def, err := p.Parse() if err != nil { t.Fatal(err) } if got, want := len(def.Elements), 6; got != want { t.Fatalf("got [%v] want [%v]", got, want) } pkg := def.Elements[4].(*Package) if got, want := pkg.Comment.Message(), " bat1"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := len(pkg.Comment.Lines), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := pkg.Comment.Lines[1], " bat2"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := len(def.Elements[5].(*Message).Comment.Lines), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestCommentInOptionValue(t *testing.T) { src := `syntax = "proto3"; message Foo { string bar = 1 [ // comment // me deprecated=true ]; }` p := newParserOn(src) def, err := p.Parse() if err != nil { t.Fatal(err) } o := def.Elements[1].(*Message).elements()[0].(*NormalField).Options[0] if got, want := o.Name, "deprecated"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := def.Elements[1].(*Message).elements()[0].(*NormalField).IsDeprecated(), true; got != want { t.Errorf("got [%v]:%T want [%v]:%T", got, got, want, want) } if got, want := len(o.Comment.Lines), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.Comment.Lines[1], " me"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestNormalFieldInlineComment(t *testing.T) { src := `message Example { /* i'm */ optional /* a comment */ bool /* too */ field /* hard */ = /* to */ 1 /* read */; }` p := newParserOn(src) def, err := p.Parse() if err != nil { t.Fatal(err) } m := def.Elements[0].(*Message) f := m.Elements[1].(*NormalField) lines := f.Field.InlineComment.Lines if got, want := len(lines), 5; got != want { t.Fatalf("got [%v:%T] want [%v:%T]", got, got, want, want) } if got, want := lines[0], " a comment "; got != want { t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want) } if got, want := lines[1], " too "; got != want { t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want) } if got, want := lines[4], " read "; got != want { t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want) } } golang-github-emicklei-proto-1.14.0/edition.go000066400000000000000000000036671473077502100212510ustar00rootroot00000000000000// Copyright (c) 2024 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) type Edition struct { Position scanner.Position Comment *Comment Value string InlineComment *Comment Parent Visitee } func (e *Edition) parse(p *Parser) error { if _, tok, lit := p.next(); tok != tEQUALS { return p.unexpected(lit, "edition =", e) } _, _, lit := p.next() if !isString(lit) { return p.unexpected(lit, "edition string constant", e) } e.Value, _ = unQuote(lit) return nil } // Accept dispatches the call to the visitor. func (e *Edition) Accept(v Visitor) { // v.VisitEdition(e) in v2 } // Doc is part of Documented func (e *Edition) Doc() *Comment { return e.Comment } // inlineComment is part of commentInliner. func (e *Edition) inlineComment(c *Comment) { e.InlineComment = c } func (e *Edition) parent(v Visitee) { e.Parent = v } golang-github-emicklei-proto-1.14.0/edition_test.go000066400000000000000000000026431473077502100223010ustar00rootroot00000000000000// Copyright (c) 2024 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestEdition(t *testing.T) { proto := `edition = "1967";` p := newParserOn(proto) pr, err := p.Parse() if err != nil { t.Fatal(err) } e := pr.elements()[0].(*Edition) if got, want := e.Value, "1967"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } golang-github-emicklei-proto-1.14.0/enum.go000066400000000000000000000133241473077502100205510ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Enum definition consists of a name and an enum body. type Enum struct { Position scanner.Position Comment *Comment Name string Elements []Visitee Parent Visitee } // Accept dispatches the call to the visitor. func (e *Enum) Accept(v Visitor) { v.VisitEnum(e) } // Doc is part of Documented func (e *Enum) Doc() *Comment { return e.Comment } // addElement is part of elementContainer func (e *Enum) addElement(v Visitee) { v.parent(e) e.Elements = append(e.Elements, v) } // elements is part of elementContainer func (e *Enum) elements() []Visitee { return e.Elements } // takeLastComment is part of elementContainer // removes and returns the last element of the list if it is a Comment. func (e *Enum) takeLastComment(expectedOnLine int) (last *Comment) { last, e.Elements = takeLastCommentIfEndsOnLine(e.Elements, expectedOnLine) return } func (e *Enum) parse(p *Parser) error { pos, tok, lit := p.next() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "enum identifier", e) } } e.Name = lit consumeCommentFor(p, e) _, tok, lit = p.next() if tok != tLEFTCURLY { return p.unexpected(lit, "enum opening {", e) } for { pos, tok, lit = p.next() switch tok { case tCOMMENT: if com := mergeOrReturnComment(e.elements(), lit, pos); com != nil { // not merged? e.addElement(com) } case tOPTION: v := new(Option) v.Position = pos v.Comment = e.takeLastComment(pos.Line) err := v.parse(p) if err != nil { return err } e.addElement(v) case tRIGHTCURLY, tEOF: goto done case tSEMICOLON: maybeScanInlineComment(p, e) case tRESERVED: r := new(Reserved) r.Position = pos r.Comment = e.takeLastComment(pos.Line - 1) if err := r.parse(p); err != nil { return err } e.addElement(r) default: p.nextPut(pos, tok, lit) f := new(EnumField) f.Position = pos f.Comment = e.takeLastComment(pos.Line - 1) err := f.parse(p) if err != nil { return err } e.addElement(f) } } done: if tok != tRIGHTCURLY { return p.unexpected(lit, "enum closing }", e) } return nil } // parent is part of elementContainer func (e *Enum) parent(p Visitee) { e.Parent = p } // EnumField is part of the body of an Enum. type EnumField struct { Position scanner.Position Comment *Comment Name string Integer int // ValueOption is deprecated, use Elements instead ValueOption *Option Elements []Visitee // such as Option and Comment InlineComment *Comment Parent Visitee } // elements is part of elementContainer func (f *EnumField) elements() []Visitee { return f.Elements } // takeLastComment is part of elementContainer // removes and returns the last element of the list if it is a Comment. func (f *EnumField) takeLastComment(expectedOnLine int) (last *Comment) { last, f.Elements = takeLastCommentIfEndsOnLine(f.Elements, expectedOnLine) return } // Accept dispatches the call to the visitor. func (f *EnumField) Accept(v Visitor) { v.VisitEnumField(f) } // inlineComment is part of commentInliner. func (f *EnumField) inlineComment(c *Comment) { f.InlineComment = c } // Doc is part of Documented func (f *EnumField) Doc() *Comment { return f.Comment } func (f *EnumField) parse(p *Parser) error { _, tok, lit := p.nextIdentifier() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "enum field identifier", f) } } f.Name = lit pos, tok, lit := p.next() if tok != tEQUALS { return p.unexpected(lit, "enum field =", f) } i, err := p.nextInteger() if err != nil { return p.unexpected(err.Error(), "enum field integer", f) } f.Integer = i pos, tok, lit = p.next() if tok == tLEFTSQUARE { for { o := new(Option) o.Position = pos o.IsEmbedded = true err := o.parse(p) if err != nil { return err } // update deprecated field with the last option found f.ValueOption = o f.addElement(o) pos, tok, lit = p.next() if tok == tCOMMA { continue } if tok == tRIGHTSQUARE { break } } } if tSEMICOLON == tok { p.nextPut(pos, tok, lit) // put back this token for scanning inline comment } return nil } // addElement is part of elementContainer func (f *EnumField) addElement(v Visitee) { v.parent(f) f.Elements = append(f.Elements, v) } func (f *EnumField) parent(v Visitee) { f.Parent = v } // IsDeprecated returns true if the option "deprecated" is set with value "true". func (f *EnumField) IsDeprecated() bool { for _, each := range f.Elements { if opt, ok := each.(*Option); ok { if opt.Name == optionNameDeprecated { return opt.Constant.Source == "true" } } } return false } golang-github-emicklei-proto-1.14.0/enum_test.go000066400000000000000000000145441473077502100216150ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "testing" ) func TestEnum(t *testing.T) { proto := ` // enum enum EnumAllowingAlias { reserved 998, 1000 to 2000; reserved "HELLO", "WORLD"; option allow_alias = true; UNKNOWN = 0; STARTED = 1; RUNNING = 2 [(custom_option) = "hello world"]; NEG = -42; SOMETHING_FOO = 0 [ (bar.enum_value_option) = true, (bar.enum_value_dep_option) = { hello: 1 } ]; }` p := newParserOn(proto) pr, err := p.Parse() if err != nil { t.Fatal(err) } enums := collect(pr).Enums() if got, want := len(enums), 1; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(enums[0].Elements), 8; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := enums[0].Comment != nil, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := enums[0].Comment.Message(), " enum"; got != want { t.Errorf("got [%v] want [%v]", enums[0].Comment, want) } if got, want := enums[0].Position.Line, 3; got != want { t.Errorf("got [%d] want [%d]", got, want) } // enum reserved ids e1 := enums[0].Elements[0].(*Reserved) if got, want := len(e1.Ranges), 2; got != want { t.Errorf("got [%d] want [%d]", got, want) } e1rg0 := e1.Ranges[0] if got, want := e1rg0.From, 998; got != want { t.Errorf("got [%d] want [%d]", got, want) } if got, want := e1rg0.From, e1rg0.To; got != want { t.Errorf("got [%d] want [%d]", got, want) } e1rg1 := e1.Ranges[1] if got, want := e1rg1.From, 1000; got != want { t.Errorf("got [%d] want [%d]", got, want) } if got, want := e1rg1.To, 2000; got != want { t.Errorf("got [%d] want [%d]", got, want) } // enum reserved field names e2 := enums[0].Elements[1].(*Reserved) if got, want := len(e2.FieldNames), 2; got != want { t.Errorf("got [%d] want [%d]", got, want) } e2fn0 := e2.FieldNames[0] if got, want := e2fn0, "HELLO"; got != want { t.Errorf("got [%s] want [%s]", got, want) } e2fn1 := e2.FieldNames[1] if got, want := e2fn1, "WORLD"; got != want { t.Errorf("got [%s] want [%s]", got, want) } // enum option checkParent(enums[0].Elements[2].(*Option), t) // enum values ef1 := enums[0].Elements[3].(*EnumField) if got, want := ef1.Integer, 0; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := ef1.Position.Line, 7; got != want { t.Errorf("got [%d] want [%d]", got, want) } ef3 := enums[0].Elements[5].(*EnumField) if got, want := ef3.Integer, 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } ef3opt := ef3.Elements[0].(*Option) if got, want := ef3opt.Name, "(custom_option)"; got != want { t.Errorf("got [%v] want [%v]", got, want) } checkParent(ef3.Elements[0].(*Option), t) // test for deprecated field if got, want := ef3opt, ef3.ValueOption; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := ef3opt.Constant.Source, "hello world"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := ef3.Position.Line, 9; got != want { t.Errorf("got [%d] want [%d]", got, want) } ef4 := enums[0].Elements[6].(*EnumField) if got, want := ef4.Integer, -42; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestEnumWithHex(t *testing.T) { src := `enum Flags { FLAG1 = 0x11; }` p := newParserOn(src) enum := new(Enum) p.next() if err := enum.parse(p); err != nil { t.Fatal(err) } if got, want := len(enum.Elements), 1; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := enum.Elements[0].(*EnumField).Integer, 17; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestEnumWithDeprecatedField(t *testing.T) { src := `enum Flags { OLD = 1 [deprecated = true]; NEW = 2; }` p := newParserOn(src) enum := new(Enum) p.next() if err := enum.parse(p); err != nil { t.Fatal(err) } if got, want := len(enum.Elements), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := enum.Elements[0].(*EnumField).IsDeprecated(), true; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := enum.Elements[1].(*EnumField).IsDeprecated(), false; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestEnumInlineCommentBeforeBody(t *testing.T) { src := `enum BarEnum // BarEnum // with another line { BAR_TYPE_INVALID= 0; BAR_TYPE_BAD = 1; } ` p := newParserOn(src) e := new(Enum) p.next() if err := e.parse(p); err != nil { t.Fatal(err) } nestedComment := e.Elements[0].(*Comment) if nestedComment == nil { t.Fatal("expected comment present") } if got, want := len(nestedComment.Lines), 2; got != want { t.Errorf("got %d want %d lines", got, want) } } func TestEnumFieldWalkWithComment(t *testing.T) { src := `enum HideIt { PRIVATE = 1 [ // hidden // field (google.api.value_visibility).restriction = "HIDDEN" ]; } ` p := newParserOn(src) e := new(Enum) p.next() if err := e.parse(p); err != nil { t.Fatal(err) } var name, msg string walk(e, WithOption(func(o *Option) { name = o.Name msg = o.Comment.Message() })) if got, want := name, "(google.api.value_visibility).restriction"; got != want { t.Errorf("got [%v]:%T want [%v]:%T", got, got, want, want) } if got, want := msg, " hidden"; got != want { t.Errorf("got [%v]:%T want [%v]:%T", got, got, want, want) } } golang-github-emicklei-proto-1.14.0/extensions.go000066400000000000000000000036271473077502100220110ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Extensions declare that a range of field numbers in a message are available for third-party extensions. // proto2 only type Extensions struct { Position scanner.Position Comment *Comment Ranges []Range InlineComment *Comment Parent Visitee } // inlineComment is part of commentInliner. func (e *Extensions) inlineComment(c *Comment) { e.InlineComment = c } // Accept dispatches the call to the visitor. func (e *Extensions) Accept(v Visitor) { v.VisitExtensions(e) } // parse expects ranges func (e *Extensions) parse(p *Parser) error { list, err := parseRanges(p, e) if err != nil { return err } e.Ranges = list return nil } // parent is part of elementContainer func (e *Extensions) parent(p Visitee) { e.Parent = p } golang-github-emicklei-proto-1.14.0/extensions_test.go000066400000000000000000000037561473077502100230530ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestExtensions(t *testing.T) { proto := `message M { // extensions extensions 4, 20 to max; // max }` p := newParserOn(proto) p.next() // consume message m := new(Message) err := m.parse(p) if err != nil { t.Fatal(err) } if len(m.Elements) != 1 { t.Fatal("1 elements expected, got", len(m.Elements), m.Elements) } f := m.Elements[0].(*Extensions) if got, want := len(f.Ranges), 2; got != want { t.Fatalf("got [%d] want [%d]", got, want) } if got, want := f.Position.Line, 3; got != want { t.Fatalf("got [%d] want [%d]", got, want) } if got, want := f.Ranges[1].SourceRepresentation(), "20 to max"; got != want { t.Errorf("got [%s] want [%s]", got, want) } if f.Comment == nil { t.Fatal("comment expected") } if got, want := f.InlineComment.Message(), " max"; got != want { t.Errorf("got [%s] want [%s]", got, want) } } golang-github-emicklei-proto-1.14.0/field.go000066400000000000000000000144771473077502100207020ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Field is an abstract message field. type Field struct { Position scanner.Position Comment *Comment Name string Type string Sequence int Options []*Option InlineComment *Comment Parent Visitee } // inlineComment is part of commentInliner. func (f *Field) inlineComment(c *Comment) { f.InlineComment = c } // NormalField represents a field in a Message. type NormalField struct { *Field Repeated bool Optional bool // proto2 Required bool // proto2 } func newNormalField() *NormalField { return &NormalField{Field: new(Field)} } // Accept dispatches the call to the visitor. func (f *NormalField) Accept(v Visitor) { v.VisitNormalField(f) } // Doc is part of Documented func (f *NormalField) Doc() *Comment { return f.Comment } // parse expects: // [ "repeated" | "optional" ] type fieldName "=" fieldNumber [ "[" fieldOptions "]" ] ";" func (f *NormalField) parse(p *Parser) error { for { pos, tok, lit := p.nextTypeName() switch tok { case tCOMMENT: c := newComment(pos, lit) if f.InlineComment == nil { f.InlineComment = c } else { f.InlineComment.Merge(c) } case tREPEATED: f.Repeated = true return f.parse(p) case tOPTIONAL: // proto2 f.Optional = true return f.parse(p) case tIDENT: f.Type = lit return parseFieldAfterType(f.Field, p, f) default: goto done } } done: return nil } // parseFieldAfterType expects: // fieldName "=" fieldNumber [ "[" fieldOptions "]" ] "; func parseFieldAfterType(f *Field, p *Parser, parent Visitee) error { expectedToken := tIDENT expected := "field identifier" for { pos, tok, lit := p.next() if tok == tCOMMENT { c := newComment(pos, lit) if f.InlineComment == nil { f.InlineComment = c } else { f.InlineComment.Merge(c) } continue } if tok != expectedToken { // allow keyword as field name if expectedToken == tIDENT && isKeyword(tok) { // continue as identifier tok = tIDENT } else { return p.unexpected(lit, expected, f) } } // found expected token if tok == tIDENT { f.Name = lit expectedToken = tEQUALS expected = "field =" continue } if tok == tEQUALS { expectedToken = tNUMBER expected = "field sequence number" continue } if tok == tNUMBER { // put it back so we can use the generic nextInteger p.nextPut(pos, tok, lit) i, err := p.nextInteger() if err != nil { return p.unexpected(lit, expected, f) } f.Sequence = i break } } consumeFieldComments(f, p) // see if there are options pos, tok, lit := p.next() if tLEFTSQUARE != tok { p.nextPut(pos, tok, lit) return nil } // consume options for { o := new(Option) o.Position = pos o.IsEmbedded = true o.parent(parent) err := o.parse(p) if err != nil { return err } f.Options = append(f.Options, o) pos, tok, lit = p.next() if tRIGHTSQUARE == tok { break } if tCOMMA != tok { return p.unexpected(lit, "option ,", o) } } return nil } func consumeFieldComments(f *Field, p *Parser) { pos, tok, lit := p.next() for tok == tCOMMENT { c := newComment(pos, lit) if f.InlineComment == nil { f.InlineComment = c } else { f.InlineComment.Merge(c) } pos, tok, lit = p.next() } // no longer a comment, put it back p.nextPut(pos, tok, lit) } // TODO copy paste func consumeOptionComments(o *Option, p *Parser) { pos, tok, lit := p.next() for tok == tCOMMENT { c := newComment(pos, lit) if o.Comment == nil { o.Comment = c } else { o.Comment.Merge(c) } pos, tok, lit = p.next() } // no longer a comment, put it back p.nextPut(pos, tok, lit) } // MapField represents a map entry in a message. type MapField struct { *Field KeyType string } func newMapField() *MapField { return &MapField{Field: new(Field)} } // Accept dispatches the call to the visitor. func (f *MapField) Accept(v Visitor) { v.VisitMapField(f) } // Doc is part of Documented func (f *MapField) Doc() *Comment { return f.Comment } // parse expects: // mapField = "map" "<" keyType "," type ">" mapName "=" fieldNumber [ "[" fieldOptions "]" ] ";" // keyType = "int32" | "int64" | "uint32" | "uint64" | "sint32" | "sint64" | // // "fixed32" | "fixed64" | "sfixed32" | "sfixed64" | "bool" | "string" func (f *MapField) parse(p *Parser) error { _, tok, lit := p.next() if tLESS != tok { return p.unexpected(lit, "map keyType <", f) } _, tok, lit = p.nextTypeName() if tIDENT != tok { return p.unexpected(lit, "map identifier", f) } f.KeyType = lit _, tok, lit = p.next() if tCOMMA != tok { return p.unexpected(lit, "map type separator ,", f) } _, tok, lit = p.nextTypeName() if tIDENT != tok { return p.unexpected(lit, "map valueType identifier", f) } f.Type = lit _, tok, lit = p.next() if tGREATER != tok { return p.unexpected(lit, "map valueType >", f) } return parseFieldAfterType(f.Field, p, f) } func (f *Field) parent(v Visitee) { f.Parent = v } const optionNameDeprecated = "deprecated" // IsDeprecated returns true if the option "deprecated" is set with value "true". func (f *Field) IsDeprecated() bool { for _, each := range f.Options { if each.Name == optionNameDeprecated { return each.Constant.Source == "true" } } return false } golang-github-emicklei-proto-1.14.0/field_test.go000066400000000000000000000147301473077502100217310ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "testing" ) func TestField(t *testing.T) { proto := `repeated foo.bar lots =1 [option1=a, option2=b, option3="happy"];` p := newParserOn(proto) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.Repeated, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Type, "foo.bar"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Name, "lots"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(f.Options), 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Name, "option1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Constant.Source, "a"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[1].Name, "option2"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[1].Constant.Source, "b"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[2].Constant.Source, "happy"; got != want { t.Errorf("got [%v] want [%v]", got, want) } checkParent(f.Options[0], t) } func TestFieldNoWhitespace(t *testing.T) { proto := `string s=1[a=b];` p := newParserOn(proto) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.Type, "string"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Sequence, 1; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Name, "a"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Constant.Source, "b"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestFieldSimple(t *testing.T) { proto := `string optional_string_piece = 24 [ctype=STRING_PIECE];` p := newParserOn(proto) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.Type, "string"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Name, "optional_string_piece"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Sequence, 24; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(f.Options), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Name, "ctype"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Constant.Source, "STRING_PIECE"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestFieldSyntaxErrors(t *testing.T) { for i, each := range []string{ `repeatet foo.bar lots = 1;`, `string lots === 1;`, } { f := newNormalField() if f.parse(newParserOn(each)) == nil { t.Errorf("uncaught syntax error in test case %d, %#v", i, f) } } } func TestMapField(t *testing.T) { proto := ` projects = 3 [foo=bar];` p := newParserOn(proto) f := newMapField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.KeyType, "string"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Type, "Project"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Name, "projects"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Sequence, 3; got != want { t.Errorf("got [%v] want [%v]", got, want) } checkParent(f.Options[0], t) } func TestMapFieldWithDotTypes(t *testing.T) { proto := ` <.Some.How, .Such.Project> projects = 3;` p := newParserOn(proto) f := newMapField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.KeyType, ".Some.How"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Type, ".Such.Project"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestOptionalWithOption(t *testing.T) { proto := `optional int32 default_int32 = 61 [default = 41 ];` p := newParserOn(proto) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.Sequence, 61; got != want { t.Errorf("got [%v] want [%v]", got, want) } o := f.Options[0] if got, want := o.Name, "default"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := o.Constant.Source, "41"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestFieldInlineComment(t *testing.T) { proto := `message Hello { // comment bool foo = 1; // inline comment }` p := newParserOn(proto) def := new(Proto) err := def.parse(p) if err != nil { t.Fatal(err) } m := def.Elements[0].(*Message) if len(m.Elements) != 1 { t.Error("expected one element", m.Elements) } f := m.Elements[0].(*NormalField) if f.InlineComment == nil { t.Error("expected inline comment") } } func TestFieldTypeStartsWithDot(t *testing.T) { proto := `.game.Resource foo = 1;` p := newParserOn(proto) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } foot := f.Field.Type if got, want := foot, ".game.Resource"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestMultiLineFieldType(t *testing.T) { src := `google.ads.googleads.v1.enums.ConversionAdjustmentTypeEnum .ConversionAdjustmentType adjustment_type = 5;` p := newParserOn(src) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } } golang-github-emicklei-proto-1.14.0/go.mod000066400000000000000000000000521473077502100203560ustar00rootroot00000000000000module github.com/emicklei/proto go 1.12 golang-github-emicklei-proto-1.14.0/group.go000066400000000000000000000054331473077502100207430ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Group represents a (proto2 only) group. // https://developers.google.com/protocol-buffers/docs/reference/proto2-spec#group_field type Group struct { Position scanner.Position Comment *Comment Name string Optional bool Repeated bool Required bool Sequence int Elements []Visitee Parent Visitee } // Accept dispatches the call to the visitor. func (g *Group) Accept(v Visitor) { v.VisitGroup(g) } // addElement is part of elementContainer func (g *Group) addElement(v Visitee) { v.parent(g) g.Elements = append(g.Elements, v) } // elements is part of elementContainer func (g *Group) elements() []Visitee { return g.Elements } // Doc is part of Documented func (g *Group) Doc() *Comment { return g.Comment } // takeLastComment is part of elementContainer // removes and returns the last element of the list if it is a Comment. func (g *Group) takeLastComment(expectedOnLine int) (last *Comment) { last, g.Elements = takeLastCommentIfEndsOnLine(g.Elements, expectedOnLine) return } // parse expects: // groupName "=" fieldNumber { messageBody } func (g *Group) parse(p *Parser) error { _, tok, lit := p.next() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "group name", g) } } g.Name = lit _, tok, lit = p.next() if tok != tEQUALS { return p.unexpected(lit, "group =", g) } i, err := p.nextInteger() if err != nil { return p.unexpected(lit, "group sequence number", g) } g.Sequence = i consumeCommentFor(p, g) _, tok, lit = p.next() if tok != tLEFTCURLY { return p.unexpected(lit, "group opening {", g) } return parseMessageBody(p, g) } func (g *Group) parent(v Visitee) { g.Parent = v } golang-github-emicklei-proto-1.14.0/group_test.go000066400000000000000000000042061473077502100217770ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestGroup(t *testing.T) { oto := `message M { // group optional group OptionalGroup = 16 // group comment 1 // group comment 2 { // field optional int32 a = 17; } }` p := newParserOn(oto) p.next() // consume first token m := new(Message) err := m.parse(p) if err != nil { t.Error(err) } if got, want := len(m.Elements), 1; got != want { t.Logf("%#v", m.Elements) t.Fatalf("got [%v] want [%v]", got, want) } g := m.Elements[0].(*Group) if got, want := len(g.Elements), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := g.Position.Line, 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := g.Comment != nil, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } f := g.Elements[1].(*NormalField) if got, want := f.Name, "a"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Optional, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } } golang-github-emicklei-proto-1.14.0/import.go000066400000000000000000000040711473077502100211160ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Import holds a filename to another .proto definition. type Import struct { Position scanner.Position Comment *Comment Filename string Kind string // weak, public, InlineComment *Comment Parent Visitee } func (i *Import) parse(p *Parser) error { _, tok, lit := p.next() switch tok { case tWEAK: i.Kind = lit return i.parse(p) case tPUBLIC: i.Kind = lit return i.parse(p) case tIDENT: i.Filename, _ = unQuote(lit) default: return p.unexpected(lit, "import classifier weak|public|quoted", i) } return nil } // Accept dispatches the call to the visitor. func (i *Import) Accept(v Visitor) { v.VisitImport(i) } // inlineComment is part of commentInliner. func (i *Import) inlineComment(c *Comment) { i.InlineComment = c } // Doc is part of Documented func (i *Import) Doc() *Comment { return i.Comment } func (i *Import) parent(v Visitee) { i.Parent = v } golang-github-emicklei-proto-1.14.0/import_test.go000066400000000000000000000030521473077502100221530ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestParseImport(t *testing.T) { proto := `import public "other.proto";` p := newParserOn(proto) p.next() // consume first token i := new(Import) err := i.parse(p) if err != nil { t.Fatal(err) } if got, want := i.Filename, "other.proto"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := i.Kind, "public"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } golang-github-emicklei-proto-1.14.0/message.go000066400000000000000000000134711473077502100212340ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Message consists of a message name and a message body. type Message struct { Position scanner.Position Comment *Comment Name string IsExtend bool Elements []Visitee Parent Visitee } func (m *Message) groupName() string { if m.IsExtend { return "extend" } return "message" } // parse expects ident { messageBody func (m *Message) parse(p *Parser) error { _, tok, lit := p.nextIdentifier() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, m.groupName()+" identifier", m) } } m.Name = lit consumeCommentFor(p, m) _, tok, lit = p.next() if tok != tLEFTCURLY { return p.unexpected(lit, m.groupName()+" opening {", m) } return parseMessageBody(p, m) } // parseMessageBody parses elements after {. It consumes the closing } func parseMessageBody(p *Parser, c elementContainer) error { var ( pos scanner.Position tok token lit string ) for { pos, tok, lit = p.next() switch { case isComment(lit): if com := mergeOrReturnComment(c.elements(), lit, pos); com != nil { // not merged? c.addElement(com) } case tENUM == tok: e := new(Enum) e.Position = pos e.Comment = c.takeLastComment(pos.Line - 1) if err := e.parse(p); err != nil { return err } c.addElement(e) case tMESSAGE == tok: msg := new(Message) msg.Position = pos msg.Comment = c.takeLastComment(pos.Line - 1) if err := msg.parse(p); err != nil { return err } c.addElement(msg) case tOPTION == tok: o := new(Option) o.Position = pos o.Comment = c.takeLastComment(pos.Line - 1) if err := o.parse(p); err != nil { return err } c.addElement(o) case tONEOF == tok: o := new(Oneof) o.Position = pos o.Comment = c.takeLastComment(pos.Line - 1) if err := o.parse(p); err != nil { return err } c.addElement(o) case tMAP == tok: f := newMapField() f.Position = pos f.Comment = c.takeLastComment(pos.Line - 1) if err := f.parse(p); err != nil { return err } c.addElement(f) case tRESERVED == tok: r := new(Reserved) r.Position = pos r.Comment = c.takeLastComment(pos.Line - 1) if err := r.parse(p); err != nil { return err } c.addElement(r) // BEGIN proto2 case tOPTIONAL == tok || tREPEATED == tok || tREQUIRED == tok: // look ahead prevTok := tok pos, tok, lit = p.next() if tGROUP == tok { g := new(Group) g.Position = pos g.Comment = c.takeLastComment(pos.Line - 1) g.Optional = prevTok == tOPTIONAL g.Repeated = prevTok == tREPEATED g.Required = prevTok == tREQUIRED if err := g.parse(p); err != nil { return err } c.addElement(g) } else { // not a group, will be tFIELD p.nextPut(pos, tok, lit) f := newNormalField() f.Type = lit f.Position = pos f.Comment = c.takeLastComment(pos.Line - 1) f.Optional = prevTok == tOPTIONAL f.Repeated = prevTok == tREPEATED f.Required = prevTok == tREQUIRED if err := f.parse(p); err != nil { return err } c.addElement(f) } case tGROUP == tok: g := new(Group) g.Position = pos g.Comment = c.takeLastComment(pos.Line - 1) if err := g.parse(p); err != nil { return err } c.addElement(g) case tEXTENSIONS == tok: e := new(Extensions) e.Position = pos e.Comment = c.takeLastComment(pos.Line - 1) if err := e.parse(p); err != nil { return err } c.addElement(e) case tEXTEND == tok: e := new(Message) e.Position = pos e.Comment = c.takeLastComment(pos.Line - 1) e.IsExtend = true if err := e.parse(p); err != nil { return err } c.addElement(e) // END proto2 only case tRIGHTCURLY == tok || tEOF == tok: goto done case tSEMICOLON == tok: maybeScanInlineComment(p, c) // continue default: // tFIELD p.nextPut(pos, tok, lit) f := newNormalField() f.Position = pos f.Comment = c.takeLastComment(pos.Line - 1) if err := f.parse(p); err != nil { return err } c.addElement(f) } } done: if tok != tRIGHTCURLY { return p.unexpected(lit, "extend|message|group closing }", c) } return nil } // Accept dispatches the call to the visitor. func (m *Message) Accept(v Visitor) { v.VisitMessage(m) } // addElement is part of elementContainer func (m *Message) addElement(v Visitee) { v.parent(m) m.Elements = append(m.Elements, v) } // elements is part of elementContainer func (m *Message) elements() []Visitee { return m.Elements } func (m *Message) takeLastComment(expectedOnLine int) (last *Comment) { last, m.Elements = takeLastCommentIfEndsOnLine(m.Elements, expectedOnLine) return } // Doc is part of Documented func (m *Message) Doc() *Comment { return m.Comment } func (m *Message) parent(v Visitee) { m.Parent = v } golang-github-emicklei-proto-1.14.0/message_test.go000066400000000000000000000165211473077502100222720ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "testing" ) func TestMessage(t *testing.T) { proto := ` message Out { // identifier string id = 1; // size int64 size = 2; oneof foo { string name = 4; SubMessage sub_message = 9; } message Inner { // Level 2 int64 ival = 1; } map proto2_value = 13; option (my_option).a = true; }` p := newParserOn(proto) p.next() // consume first token m := new(Message) err := m.parse(p) if err != nil { t.Fatal(err) } if got, want := m.Name, "Out"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(m.Elements), 6; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := m.Elements[0].(*NormalField).Position.String(), ":4:3"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := m.Elements[0].(*NormalField).Comment.Position.String(), ":3:3"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := m.Elements[3].(*Message).Position.String(), ":12:3"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := m.Elements[3].(*Message).Elements[0].(*NormalField).Position.Line, 13; got != want { t.Errorf("got [%v] want [%v]", got, want) } checkParent(m.Elements[5].(*Option), t) } func TestRepeatedGroupInMessage(t *testing.T) { src := `message SearchResponse { repeated group Result = 1 { required string url = 2; optional string title = 3; repeated string snippets = 4; } }` p := newParserOn(src) p.next() // consume first token m := new(Message) err := m.parse(p) if err != nil { t.Error(err) } if got, want := len(m.Elements), 1; got != want { t.Logf("%#v", m.Elements) t.Fatalf("got [%v] want [%v]", got, want) } g := m.Elements[0].(*Group) if got, want := len(g.Elements), 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := g.Repeated, true; got != want { t.Fatalf("got Repeated [%v] want [%v]", got, want) } } func TestRequiredGroupInMessage(t *testing.T) { src := `message SearchResponse { required group Result = 1 { required string url = 2; optional string title = 3; repeated string snippets = 4; } }` p := newParserOn(src) p.next() // consume first token m := new(Message) err := m.parse(p) if err != nil { t.Error(err) } if got, want := len(m.Elements), 1; got != want { t.Logf("%#v", m.Elements) t.Fatalf("got [%v] want [%v]", got, want) } g := m.Elements[0].(*Group) if got, want := len(g.Elements), 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := g.Required, true; got != want { t.Fatalf("got Required [%v] want [%v]", got, want) } } func TestSingleQuotedReservedNames(t *testing.T) { src := `message Channel { reserved '', 'things', ""; }` p := newParserOn(src) p.next() // consume first token m := new(Message) err := m.parse(p) if err != nil { t.Error(err) } r := m.Elements[0].(*Reserved) if got, want := r.FieldNames[0], ""; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := r.FieldNames[1], "things"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := r.FieldNames[2], ""; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestMessageInlineCommentBeforeBody(t *testing.T) { src := `message BarMessage // BarMessage // with another line { name string = 1; } ` p := newParserOn(src) msg := new(Message) p.next() if err := msg.parse(p); err != nil { t.Fatal(err) } nestedComment := msg.Elements[0].(*Comment) if nestedComment == nil { t.Fatal("expected comment present") } if got, want := len(nestedComment.Lines), 2; got != want { t.Errorf("got %d want %d lines", got, want) } } func TestMessageWithMessage(t *testing.T) { src := `message message { string message = 1; } ` p := newParserOn(src) msg := new(Message) p.next() if err := msg.parse(p); err != nil { t.Fatal(err) } if got, want := msg.Name, "message"; got != want { t.Errorf("got %s want %s", got, want) } if got, want := len(msg.Elements), 1; got != want { t.Errorf("got %d want %d elements", got, want) } f := msg.Elements[0].(*NormalField) if got, want := f.Name, "message"; got != want { t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want) } } func TestIssue143_Key(t *testing.T) { src := `message Msg { option (option_name) = { [key]: value_name }; }` p := newParserOn(src) msg := new(Message) p.next() if err := msg.parse(p); err != nil { t.Fatal(err) } name := msg.Elements[0].(*Option).AggregatedConstants[0].Name value := msg.Elements[0].(*Option).AggregatedConstants[0].Literal.Source if got, want := name, "key"; got != want { t.Errorf("got %s want %s", got, want) } if got, want := value, "value_name"; got != want { t.Errorf("got %s want %s", got, want) } } func TestIssue143_KeyDot(t *testing.T) { src := `message Msg { option (option_name) = { [key.dot]: value_name }; }` p := newParserOn(src) msg := new(Message) p.next() if err := msg.parse(p); err != nil { t.Fatal(err) } name := msg.Elements[0].(*Option).AggregatedConstants[0].Name value := msg.Elements[0].(*Option).AggregatedConstants[0].Literal.Source if got, want := name, "key.dot"; got != want { t.Errorf("got %s want %s", got, want) } if got, want := value, "value_name"; got != want { t.Errorf("got %s want %s", got, want) } } func TestIssue143_Keyword(t *testing.T) { src := `message Msg { option (option_name) = { [option.message]: repeated }; }` p := newParserOn(src) msg := new(Message) p.next() if err := msg.parse(p); err != nil { t.Fatal(err) } name := msg.Elements[0].(*Option).AggregatedConstants[0].Name value := msg.Elements[0].(*Option).AggregatedConstants[0].Literal.Source if got, want := name, "option.message"; got != want { t.Errorf("got %s want %s", got, want) } if got, want := value, "repeated"; got != want { t.Errorf("got %s want %s", got, want) } } func TestCommentsInFieldOptionsArray(t *testing.T) { src := `message Msg { repeated string strings_list = 5 [ // before (validate.rules).repeated.max_items = 20 // inline // after ]; }` p := newParserOn(src) msg := new(Message) p.next() if err := msg.parse(p); err != nil { t.Fatal(err) } } golang-github-emicklei-proto-1.14.0/noop_visitor.go000066400000000000000000000060231473077502100223350ustar00rootroot00000000000000// Copyright (c) 2022 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto var _ Visitor = NoopVisitor{} // NoopVisitor is a no-operation visitor that can be used when creating your own visitor that is interested in only one or a few types. // It implements the Visitor interface. type NoopVisitor struct{} // VisitMessage is part of Visitor interface func (n NoopVisitor) VisitMessage(m *Message) {} // VisitService is part of Visitor interface func (n NoopVisitor) VisitService(v *Service) {} // VisitSyntax is part of Visitor interface func (n NoopVisitor) VisitSyntax(s *Syntax) {} // VisitSyntax is part of Visitor interface func (n NoopVisitor) VisitEdition(e *Edition) {} // VisitPackage is part of Visitor interface func (n NoopVisitor) VisitPackage(p *Package) {} // VisitOption is part of Visitor interface func (n NoopVisitor) VisitOption(o *Option) {} // VisitImport is part of Visitor interface func (n NoopVisitor) VisitImport(i *Import) {} // VisitNormalField is part of Visitor interface func (n NoopVisitor) VisitNormalField(i *NormalField) {} // VisitEnumField is part of Visitor interface func (n NoopVisitor) VisitEnumField(i *EnumField) {} // VisitEnum is part of Visitor interface func (n NoopVisitor) VisitEnum(e *Enum) {} // VisitComment is part of Visitor interface func (n NoopVisitor) VisitComment(e *Comment) {} // VisitOneof is part of Visitor interface func (n NoopVisitor) VisitOneof(o *Oneof) {} // VisitOneofField is part of Visitor interface func (n NoopVisitor) VisitOneofField(o *OneOfField) {} // VisitReserved is part of Visitor interface func (n NoopVisitor) VisitReserved(r *Reserved) {} // VisitRPC is part of Visitor interface func (n NoopVisitor) VisitRPC(r *RPC) {} // VisitMapField is part of Visitor interface func (n NoopVisitor) VisitMapField(f *MapField) {} // VisitGroup is part of Visitor interface func (n NoopVisitor) VisitGroup(g *Group) {} // VisitExtensions is part of Visitor interface func (n NoopVisitor) VisitExtensions(e *Extensions) {} golang-github-emicklei-proto-1.14.0/oneof.go000066400000000000000000000076451473077502100207240ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Oneof is a field alternate. type Oneof struct { Position scanner.Position Comment *Comment Name string Elements []Visitee Parent Visitee } // addElement is part of elementContainer func (o *Oneof) addElement(v Visitee) { v.parent(o) o.Elements = append(o.Elements, v) } // elements is part of elementContainer func (o *Oneof) elements() []Visitee { return o.Elements } // takeLastComment is part of elementContainer // removes and returns the last element of the list if it is a Comment. func (o *Oneof) takeLastComment(expectedOnLine int) (last *Comment) { last, o.Elements = takeLastCommentIfEndsOnLine(o.Elements, expectedOnLine) return last } // parse expects: // oneofName "{" { oneofField | emptyStatement } "}" func (o *Oneof) parse(p *Parser) error { pos, tok, lit := p.next() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "oneof identifier", o) } } o.Name = lit consumeCommentFor(p, o) pos, tok, lit = p.next() if tok != tLEFTCURLY { return p.unexpected(lit, "oneof opening {", o) } for { pos, tok, lit = p.nextTypeName() switch tok { case tCOMMENT: if com := mergeOrReturnComment(o.elements(), lit, pos); com != nil { // not merged? o.addElement(com) } case tIDENT: f := newOneOfField() f.Position = pos f.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1) // TODO call takeLastComment instead? f.Type = lit if err := parseFieldAfterType(f.Field, p, f); err != nil { return err } o.addElement(f) case tGROUP: g := new(Group) g.Position = pos g.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1) if err := g.parse(p); err != nil { return err } o.addElement(g) case tOPTION: opt := new(Option) opt.Position = pos opt.Comment, o.Elements = takeLastCommentIfEndsOnLine(o.elements(), pos.Line-1) if err := opt.parse(p); err != nil { return err } o.addElement(opt) case tSEMICOLON: maybeScanInlineComment(p, o) // continue default: goto done } } done: if tok != tRIGHTCURLY { return p.unexpected(lit, "oneof closing }", o) } return nil } // Accept dispatches the call to the visitor. func (o *Oneof) Accept(v Visitor) { v.VisitOneof(o) } // Doc is part of Documented func (o *Oneof) Doc() *Comment { return o.Comment } // OneOfField is part of Oneof. type OneOfField struct { *Field } func newOneOfField() *OneOfField { return &OneOfField{Field: new(Field)} } // Accept dispatches the call to the visitor. func (o *OneOfField) Accept(v Visitor) { v.VisitOneofField(o) } // Doc is part of Documented // Note: although Doc() is defined on Field, it must be implemented here as well. func (o *OneOfField) Doc() *Comment { return o.Comment } func (o *Oneof) parent(v Visitee) { o.Parent = v } golang-github-emicklei-proto-1.14.0/oneof_test.go000066400000000000000000000122311473077502100217460ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestOneof(t *testing.T) { proto := `oneof foo { // just a name string name = 4; SubMessage sub_message = 9 [options=none]; }` p := newParserOn(proto) p.next() // consume first token o := new(Oneof) err := o.parse(p) if err != nil { t.Fatal(err) } if got, want := o.Name, "foo"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(o.Elements), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } first := o.Elements[0].(*OneOfField) if got, want := first.Comment.Message(), " just a name"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := first.Position.Line, 3; got != want { t.Errorf("got [%v] want [%v]", got, want) } second := o.Elements[1].(*OneOfField) if got, want := second.Name, "sub_message"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := second.Type, "SubMessage"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := second.Sequence, 9; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := second.Position.Line, 4; got != want { t.Errorf("got [%v] want [%v]", got, want) } checkParent(second.Options[0], t) } func TestFieldOneofImported(t *testing.T) { fieldType := "foo.bar" proto := `message Value { oneof value { ` + fieldType + ` value = 1; } }` p := newParserOn(proto) def := new(Proto) err := def.parse(p) if err != nil { t.Fatal(err) } m := def.Elements[0].(*Message) if len(m.Elements) != 1 { t.Errorf("expected one element but got %d", len(m.Elements)) } o := m.Elements[0].(*Oneof) if len(o.Elements) != 1 { t.Errorf("expected one element but got %d", len(o.Elements)) } f := o.Elements[0].(*OneOfField) if got, want := f.Type, fieldType; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Name, "value"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestFieldOneofDotImported(t *testing.T) { fieldType := ".foo.bar" proto := `message Value { oneof value { ` + fieldType + ` value = 1; } }` p := newParserOn(proto) def := new(Proto) err := def.parse(p) if err != nil { t.Fatal(err) } m := def.Elements[0].(*Message) if len(m.Elements) != 1 { t.Errorf("expected one element but got %d", len(m.Elements)) } o := m.Elements[0].(*Oneof) if len(o.Elements) != 1 { t.Errorf("expected one element but got %d", len(o.Elements)) } f := o.Elements[0].(*OneOfField) if got, want := f.Type, fieldType; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Name, "value"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestOneOfWithOption(t *testing.T) { src := `oneof AnOneof { option (oneof_opt1) = -99; int32 oneof_field = 2; }` p := newParserOn(src) p.next() oneof := new(Oneof) err := oneof.parse(p) if err != nil { t.Fatal(err) } option := oneof.Elements[0].(*Option) if got, want := option.Name, "(oneof_opt1)"; got != want { t.Errorf("got [%v] want [%v]", got, want) } checkParent(option, t) } func TestOneofInlineCommentBeforeBody(t *testing.T) { src := `oneof BarOption // BarOption // with another line { name string = 1; } ` p := newParserOn(src) oneof := new(Oneof) p.next() if err := oneof.parse(p); err != nil { t.Fatal(err) } nestedComment := oneof.Elements[0].(*Comment) if nestedComment == nil { t.Fatal("expected comment present") } if got, want := len(nestedComment.Lines), 2; got != want { t.Errorf("got %d want %d lines", got, want) } } func TestOneOfDocumented(t *testing.T) { src := `message Value { // documented oneof Foo { int32 oneof_field = 1; } }` p := newParserOn(src) def := new(Proto) err := def.parse(p) if err != nil { t.Fatal(err) } m := def.Elements[0].(*Message) if len(m.Elements) != 1 { t.Errorf("expected one element but got %d", len(m.Elements)) } o := m.Elements[0].(*Oneof) if len(o.Elements) != 1 { t.Errorf("expected one element but got %d", len(o.Elements)) } if Documented(o).Doc() == nil { t.Fatal("doc expected") } } golang-github-emicklei-proto-1.14.0/option.go000066400000000000000000000244641473077502100211240ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "bytes" "fmt" "sort" "text/scanner" ) // Option is a protoc compiler option type Option struct { Position scanner.Position Comment *Comment Name string Constant Literal IsEmbedded bool // AggregatedConstants is DEPRECATED. These Literals are populated into Constant.OrderedMap AggregatedConstants []*NamedLiteral InlineComment *Comment Parent Visitee } // parse reads an Option body // ( ident | //... | "(" fullIdent ")" ) { "." ident } "=" constant ";" func (o *Option) parse(p *Parser) error { consumeOptionComments(o, p) pos, tok, lit := p.nextIdentifier() if tLEFTPAREN == tok { pos, tok, lit = p.nextIdentifier() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "option full identifier", o) } } pos, tok, _ = p.next() if tok != tRIGHTPAREN { return p.unexpected(lit, "option full identifier closing )", o) } o.Name = fmt.Sprintf("(%s)", lit) } else { // non full ident if tIDENT != tok { if !isKeyword(tok) { return p.unexpected(lit, "option identifier", o) } } o.Name = lit } pos, tok, lit = p.next() if tDOT == tok { // extend identifier pos, tok, lit = p.nextIdent(true) // keyword allowed as start if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "option postfix identifier", o) } } o.Name = fmt.Sprintf("%s.%s", o.Name, lit) pos, tok, lit = p.next() } if tEQUALS != tok { return p.unexpected(lit, "option value assignment =", o) } r := p.peekNonWhitespace() var err error // values of an option can have illegal escape sequences // for the standard Go scanner used by this package. p.ignoreIllegalEscapesWhile(func() { if '{' == r { // aggregate p.next() // consume { err = o.parseAggregate(p) } else { // non aggregate l := new(Literal) l.Position = pos if e := l.parse(p); e != nil { err = e } o.Constant = *l } }) consumeOptionComments(o, p) return err } // inlineComment is part of commentInliner. func (o *Option) inlineComment(c *Comment) { o.InlineComment = c } // Accept dispatches the call to the visitor. func (o *Option) Accept(v Visitor) { v.VisitOption(o) } // Doc is part of Documented func (o *Option) Doc() *Comment { return o.Comment } // Literal represents intLit,floatLit,strLit or boolLit or a nested structure thereof. type Literal struct { Position scanner.Position Source string IsString bool // It not nil then the entry is actually a comment with line(s) // modelled this way because Literal is not an elementContainer Comment *Comment // The rune use to delimit the string value (only valid iff IsString) QuoteRune rune // literal value can be an array literal value (even nested) Array []*Literal // literal value can be a map of literals (even nested) // DEPRECATED: use OrderedMap instead Map map[string]*Literal // literal value can be a map of literals (even nested) // this is done as pairs of name keys and literal values so the original ordering is preserved OrderedMap LiteralMap } var emptyRune rune // LiteralMap is like a map of *Literal but preserved the ordering. // Can be iterated yielding *NamedLiteral values. type LiteralMap []*NamedLiteral // Get returns a Literal from the map. func (m LiteralMap) Get(key string) (*Literal, bool) { for _, each := range m { if each.Name == key { // exit on the first match return each.Literal, true } } return new(Literal), false } // SourceRepresentation returns the source (use the same rune that was used to delimit the string). func (l Literal) SourceRepresentation() string { var buf bytes.Buffer if l.IsString { if l.QuoteRune == emptyRune { buf.WriteRune('"') } else { buf.WriteRune(l.QuoteRune) } } buf.WriteString(l.Source) if l.IsString { if l.QuoteRune == emptyRune { buf.WriteRune('"') } else { buf.WriteRune(l.QuoteRune) } } return buf.String() } // parse expects to read a literal constant after =. func (l *Literal) parse(p *Parser) error { pos, tok, lit := p.next() // handle special element inside literal, a comment line if isComment(lit) { nc := newComment(pos, lit) if l.Comment == nil { l.Comment = nc } else { l.Comment.Merge(nc) } // continue with remaining entries return l.parse(p) } if tok == tLEFTSQUARE { // collect array elements array := []*Literal{} // if it's an empty array, consume the close bracket, set the Array to // an empty array, and return r := p.peekNonWhitespace() if r == ']' { pos, _, _ := p.next() l.Array = array l.IsString = false l.Position = pos return nil } for { e := new(Literal) if err := e.parse(p); err != nil { return err } array = append(array, e) _, tok, lit := p.next() if tok == tCOMMA { continue } if tok == tRIGHTSQUARE { break } return p.unexpected(lit, ", or ]", l) } l.Array = array l.IsString = false l.Position = pos return nil } if tLEFTCURLY == tok { l.Position, l.Source, l.IsString = pos, "", false constants, err := parseAggregateConstants(p, l) if err != nil { return nil } l.OrderedMap = LiteralMap(constants) return nil } if "-" == lit { // negative number if err := l.parse(p); err != nil { return err } // modify source and position l.Position, l.Source = pos, "-"+l.Source return nil } source := lit iss := isString(lit) if iss { source, l.QuoteRune = unQuote(source) } l.Position, l.Source, l.IsString = pos, source, iss // peek for multiline strings for { pos, tok, lit := p.next() if isString(lit) { line, _ := unQuote(lit) l.Source += line } else { p.nextPut(pos, tok, lit) break } } return nil } // NamedLiteral associates a name with a Literal type NamedLiteral struct { *Literal Name string // PrintsColon is true when the Name must be printed with a colon suffix PrintsColon bool } // parseAggregate reads options written using aggregate syntax. // tLEFTCURLY { has been consumed func (o *Option) parseAggregate(p *Parser) error { constants, err := parseAggregateConstants(p, o) literalMap := map[string]*Literal{} for _, each := range constants { literalMap[each.Name] = each.Literal } o.Constant = Literal{Map: literalMap, OrderedMap: constants, Position: o.Position} // reconstruct the old, deprecated field o.AggregatedConstants = collectAggregatedConstants(literalMap) return err } // flatten the maps of each literal, recursively // this func exists for deprecated Option.AggregatedConstants. func collectAggregatedConstants(m map[string]*Literal) (list []*NamedLiteral) { for k, v := range m { if v.Map != nil { sublist := collectAggregatedConstants(v.Map) for _, each := range sublist { list = append(list, &NamedLiteral{ Name: k + "." + each.Name, PrintsColon: true, Literal: each.Literal, }) } } else { list = append(list, &NamedLiteral{ Name: k, PrintsColon: true, Literal: v, }) } } // sort list by position of literal sort.Sort(byPosition(list)) return } type byPosition []*NamedLiteral func (b byPosition) Less(i, j int) bool { return b[i].Literal.Position.Line < b[j].Literal.Position.Line } func (b byPosition) Len() int { return len(b) } func (b byPosition) Swap(i, j int) { b[i], b[j] = b[j], b[i] } func parseAggregateConstants(p *Parser, container interface{}) (list []*NamedLiteral, err error) { for { _, tok, lit := p.nextMessageLiteralFieldName() // if tRIGHTSQUARE == tok { // p.nextPut(pos, tok, lit) // // caller has checked for open square ; will consume rightsquare, rightcurly and semicolon // return // } if tRIGHTCURLY == tok { return } if tSEMICOLON == tok { // just consume it continue //return } if tCOMMENT == tok { // assign to last parsed literal // TODO: see TestUseOfSemicolonsInAggregatedConstants continue } if tCOMMA == tok { if len(list) == 0 { err = p.unexpected(lit, "non-empty option aggregate key", container) return } continue } if tIDENT != tok && !isKeyword(tok) { err = p.unexpected(lit, "option aggregate key", container) return } // workaround issue #59 TODO if isString(lit) && len(list) > 0 { // concatenate with previous constant s, _ := unQuote(lit) list[len(list)-1].Source += s continue } key := lit printsColon := false // expect colon, aggregate or plain literal pos, tok, lit := p.next() if tCOLON == tok { // consume it printsColon = true pos, tok, lit = p.next() } // see if nested aggregate is started if tLEFTCURLY == tok { nested, fault := parseAggregateConstants(p, container) if fault != nil { err = fault return } // create the map m := map[string]*Literal{} for _, each := range nested { m[each.Name] = each.Literal } list = append(list, &NamedLiteral{ Name: key, PrintsColon: printsColon, Literal: &Literal{Map: m, OrderedMap: LiteralMap(nested)}}) continue } // no aggregate, put back token p.nextPut(pos, tok, lit) // now we see plain literal l := new(Literal) l.Position = pos if err = l.parse(p); err != nil { return } list = append(list, &NamedLiteral{Name: key, Literal: l, PrintsColon: printsColon}) } } func (o *Option) parent(v Visitee) { o.Parent = v } golang-github-emicklei-proto-1.14.0/option_test.go000066400000000000000000000524501473077502100221570ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "testing" ) func TestOptionCases(t *testing.T) { for i, each := range []struct { proto string name string strLit string nonStrLit string }{{ `option (full).java_package = "com.example.foo";`, "(full).java_package", "com.example.foo", "", }, { `option Bool = true;`, "Bool", "", "true", }, { `option Float = -3.14E1;`, "Float", "", "-3.14E1", }, { `option (foo_options) = { opt1: 123 opt2: "baz" };`, "(foo_options)", "", "", }, { `option foo = []`, "foo", "", "", }, { `option optimize_for = SPEED;`, "optimize_for", "", "SPEED", }, { "option (my.enum.service.is.like).rpc = 1;", "(my.enum.service.is.like).rpc", "", "1", }, { `option (imported.oss.package).action = "literal-double-quotes";`, "(imported.oss.package).action", "literal-double-quotes", "", }, { `option (imported.oss.package).action = "key:\"literal-double-quotes-escaped\"";`, "(imported.oss.package).action", `key:\"literal-double-quotes-escaped\"`, "", }, { `option (imported.oss.package).action = 'literalsinglequotes';`, "(imported.oss.package).action", "literalsinglequotes", "", }, { `option (imported.oss.package).action = 'single-quotes.with/symbols';`, "(imported.oss.package).action", "single-quotes.with/symbols", "", }} { p := newParserOn(each.proto) pr, err := p.Parse() if err != nil { t.Fatal("testcase failed:", i, err) } if got, want := len(pr.Elements), 1; got != want { t.Fatalf("[%d] got [%v] want [%v]", i, got, want) } o := pr.Elements[0].(*Option) if got, want := o.Name, each.name; got != want { t.Errorf("[%d] got [%v] want [%v]", i, got, want) } if len(each.strLit) > 0 { if got, want := o.Constant.Source, each.strLit; got != want { t.Errorf("[%d] got [%v] want [%v]", i, got, want) } } if len(each.nonStrLit) > 0 { if got, want := o.Constant.Source, each.nonStrLit; got != want { t.Errorf("[%d] got [%v] want [%v]", i, got, want) } } if got, want := o.IsEmbedded, false; got != want { t.Errorf("[%d] got [%v] want [%v]", i, got, want) } } } func TestLiteralString(t *testing.T) { proto := `"string"` p := newParserOn(proto) l := new(Literal) if err := l.parse(p); err != nil { t.Fatal(err) } if got, want := l.IsString, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := l.Source, "string"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestOptionComments(t *testing.T) { proto := ` // comment option Help = "me"; // inline` p := newParserOn(proto) pr, err := p.Parse() if err != nil { t.Fatal(err) } o := pr.Elements[0].(*Option) if got, want := o.IsEmbedded, false; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := o.Comment != nil, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.Comment.Lines[0], " comment"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.InlineComment != nil, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.InlineComment.Lines[0], " inline"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.Position.Line, 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.Comment.Position.Line, 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.InlineComment.Position.Line, 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestAggregateSyntax(t *testing.T) { proto := ` // usage: message Bar { // alternative aggregate syntax (uses TextFormat): int32 b = 2 [(foo_options) = { opt1: 123, opt2: "baz" }]; } ` p := newParserOn(proto) pr, err := p.Parse() if err != nil { t.Fatal(err) } o := pr.Elements[0].(*Message) f := o.Elements[0].(*NormalField) if got, want := len(f.Options), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } ac := f.Options[0].Constant.Map if got, want := len(ac), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := ac["opt1"].Source, "123"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.Position.Line, 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.Comment.Position.String(), ":2:1"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := f.Position.String(), ":5:3"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Constant.Position.Line, 5; got != want { t.Fatalf("got [%v] want [%v]", got, want) } // check for AggregatedConstants list := f.Options[0].AggregatedConstants if got, want := list[0].Source, "123"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := list[1].Source, "baz"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestNonPrimitiveOptionComment(t *testing.T) { proto := ` // comment option Help = { string_field: "value" }; // inline` p := newParserOn(proto) pr, err := p.Parse() if err != nil { t.Fatal(err) } o := pr.Elements[0].(*Option) if got, want := o.Comment != nil, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.Comment.Lines[0], " comment"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.InlineComment != nil, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := o.InlineComment.Lines[0], " inline"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestFieldCustomOptions(t *testing.T) { proto := `foo.bar lots = 1 [foo={hello:1}, bar=2];` p := newParserOn(proto) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.Type, "foo.bar"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Name, "lots"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(f.Options), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Name, "foo"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[1].Name, "bar"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[1].Constant.Source, "2"; got != want { t.Errorf("got [%v] want [%v]", got, want) } // check for AggregatedConstants if got, want := f.Options[0].AggregatedConstants[0].Name, "hello"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].AggregatedConstants[0].PrintsColon, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].AggregatedConstants[0].Source, "1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestIgnoreIllegalEscapeCharsInAggregatedConstants(t *testing.T) { src := `syntax = "proto3"; message Person { string name = 3 [(validate.rules).string = { pattern: "^[^\d\s]+( [^\d\s]+)*$", max_bytes: 256, }]; }` p := newParserOn(src) d, err := p.Parse() if err != nil { t.Fatal(err) } f := d.Elements[1].(*Message).Elements[0].(*NormalField) if got, want := f.Options[0].Name, "(validate.rules).string"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(f.Options[0].Constant.Map), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Constant.Map["pattern"].Source, "^[^\\d\\s]+( [^\\d\\s]+)*$"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(f.Options[0].Constant.OrderedMap), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Constant.OrderedMap[0].Name, "pattern"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].Constant.OrderedMap[0].Source, "^[^\\d\\s]+( [^\\d\\s]+)*$"; got != want { t.Errorf("got [%v] want [%v]", got, want) } // check for AggregatedConstants if got, want := f.Options[0].AggregatedConstants[0].Name, "pattern"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].AggregatedConstants[0].PrintsColon, true; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := f.Options[0].AggregatedConstants[0].Source, "^[^\\d\\s]+( [^\\d\\s]+)*$"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestIgnoreIllegalEscapeCharsInConstant(t *testing.T) { src := `syntax = "proto2"; message Person { optional string cpp_trigraph = 20 [default = "? \? ?? \?? \??? ??/ ?\?-"]; }` p := newParserOn(src) d, err := p.Parse() if err != nil { t.Fatal(err) } f := d.Elements[1].(*Message).Elements[0].(*NormalField) if got, want := f.Options[0].Constant.Source, "? \\? ?? \\?? \\??? ??/ ?\\?-"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestFieldCustomOptionExtendedIdent(t *testing.T) { proto := `Type field = 1 [(validate.rules).enum.defined_only = true];` p := newParserOn(proto) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.Options[0].Name, "(validate.rules).enum.defined_only"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } // issue #50 func TestNestedAggregateConstants(t *testing.T) { src := `syntax = "proto3"; package baz; option (foo.bar) = { woot: 100 foo { hello: 200 hello2: 300 bar { hello3: 400 } } };` p := newParserOn(src) proto, err := p.Parse() if err != nil { t.Error(err) } option := proto.Elements[2].(*Option) if got, want := option.Name, "(foo.bar)"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(option.Constant.Map), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } m := option.Constant.Map if got, want := m["woot"].Source, "100"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(m["foo"].Map), 3; got != want { t.Errorf("got [%v] want [%v]", got, want) } m = m["foo"].Map if got, want := len(m["bar"].Map), 1; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := m["bar"].Map["hello3"].Source, "400"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := option.Constant.OrderedMap[1].Name, "foo"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := option.Constant.OrderedMap[1].OrderedMap[2].Name, "bar"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := option.Constant.OrderedMap[1].OrderedMap[2].OrderedMap[0].Source, "400"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(option.AggregatedConstants), 4; got != want { t.Errorf("got [%v] want [%v]", got, want) } // for _, each := range option.AggregatedConstants { // t.Logf("%#v=%v\n", each, each.SourceRepresentation()) // } } // Issue #59 func TestMultiLineOptionAggregateValue(t *testing.T) { src := `rpc ListTransferLogs(ListTransferLogsRequest) returns (ListTransferLogsResponse) { option (google.api.http) = { get: "/v1/{parent=projects/*/locations/*/transferConfigs/*/runs/*}/" "transferLogs" }; }` p := newParserOn(src) rpc := new(RPC) p.next() err := rpc.parse(p) if err != nil { t.Error(err) } get := rpc.Options[0].Constant.Map["get"] if got, want := get.Source, "/v1/{parent=projects/*/locations/*/transferConfigs/*/runs/*}/transferLogs"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } // issue #76 func TestOptionAggregateCanUseKeyword(t *testing.T) { src := `message User { string email = 3 [(validate.field) = {required: true}]; }` p := newParserOn(src) _, err := p.Parse() if err != nil { t.Error(err) } } // issue #77 func TestOptionAggregateWithRepeatedValues(t *testing.T) { src := `message Envelope { int64 not_in = 15 [(validate.rules).int64 = {not_in: [40, 45]}]; int64 in = 16 [(validate.rules).int64 = {in: [[1],[2]]}]; }` p := newParserOn(src) def, err := p.Parse() if err != nil { t.Error(err) } field := def.Elements[0].(*Message).Elements[0].(*NormalField) notIn := field.Options[0].Constant.Map["not_in"] if got, want := len(notIn.Array), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := notIn.Array[0].Source, "40"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := notIn.Array[1].Source, "45"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestInvalidOptionAggregateWithRepeatedValues(t *testing.T) { src := `message Bogus { int64 a = 1 [a = {not_in: [40 syntax]}]; }` p := newParserOn(src) _, err := p.Parse() if err == nil { t.Error("expected syntax error") } } // issue #79 func TestUseOfSemicolonsInAggregatedConstants(t *testing.T) { src := `rpc Test(Void) returns (Void) { option (google.api.http) = { post: "/api/v1/test"; body: "*"; // ignored comment }; }` p := newParserOn(src) rpc := new(RPC) p.next() err := rpc.parse(p) if err != nil { t.Fatal(err) } if got, want := len(rpc.Elements), 1; got != want { t.Errorf("got [%v] want [%v]", got, want) } opt := rpc.Elements[0].(*Option) if got, want := len(opt.Constant.Map), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } // old access to map if got, want := opt.Constant.Map["body"].Source, "*"; got != want { t.Errorf("got [%v] want [%v]", got, want) } // new access to map body, ok := opt.Constant.OrderedMap.Get("body") if !ok { t.Fatal("expected body key") } if got, want := body.Source, "*"; got != want { t.Errorf("got [%v] want [%v]", got, want) } // for _, each := range opt.Constant.OrderedMap { // t.Log(each) // } } func TestParseNestedSelectorInAggregatedConstant(t *testing.T) { src := `rpc Test(Void) returns (Void) { option (google.api.http) = { get: "/api/v1/test" additional_bindings.post: "/api/v1/test" additional_bindings.body: "*" }; }` p := newParserOn(src) rpc := new(RPC) p.next() err := rpc.parse(p) if err != nil { t.Fatal(err) } if got, want := rpc.Options[0].Constant.Map["get"].Source, "/api/v1/test"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := rpc.Options[0].Constant.Map["additional_bindings.post"].Source, "/api/v1/test"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := rpc.Options[0].AggregatedConstants[2].Name, "additional_bindings.body"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := rpc.Options[0].AggregatedConstants[2].Source, "*"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestParseMultilineStringConstant(t *testing.T) { src := `message Test { string description = 3 [ (common.ui_field_desc) = "Description of the account" " domain (e.g. Team," "Name User Account Directory)." ]; }` p := newParserOn(src) m := new(Message) p.next() err := m.parse(p) if err != nil { t.Fatal(err) } s := m.Elements[0].(*NormalField).Options[0].Constant.Source if got, want := s, "Description of the account domain (e.g. Team,Name User Account Directory)."; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestOptionWithRepeatedMessageValues(t *testing.T) { src := `message Foo { int64 a = 1 [b = {repeated_message_field: [{hello: 1}, {hello: 2}]}]; }` p := newParserOn(src) def, err := p.Parse() if err != nil { t.Errorf("expected no error but got %v", err) } opt := def.Elements[0].(*Message).Elements[0].(*NormalField).Options[0] hello, ok := opt.AggregatedConstants[0].Array[0].OrderedMap.Get("hello") if !ok { t.Fail() } if got, want := hello.SourceRepresentation(), "1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestOptionWithRepeatedMessageValuesWithArray(t *testing.T) { src := `message Foo { int64 a = 1 [ (bar.repeated_field_dep_option) = { hello: 1, repeated_dep: [ { hello: 1, repeated_bar: [1, 2] }, { hello: 3, repeated_bar: [3, 4] } ] } ]; }` p := newParserOn(src) def, err := p.Parse() if err != nil { t.Errorf("expected no error but got %v", err) } opt := def.Elements[0].(*Message).Elements[0].(*NormalField).Options[0] hello, ok := opt.Constant.OrderedMap.Get("hello") if !ok { t.Fail() } if got, want := hello.SourceRepresentation(), "1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } repeatedDep, ok := opt.Constant.OrderedMap.Get("repeated_dep") if !ok { t.Fail() } if got, want := len(repeatedDep.Array), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } hello, ok = repeatedDep.Array[0].OrderedMap.Get("hello") if !ok { t.Fail() } if got, want := hello.SourceRepresentation(), "1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } onetwo, ok := repeatedDep.Array[0].OrderedMap.Get("repeated_bar") if !ok { t.Fail() } if got, want := onetwo.Array[0].Source, "1"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := onetwo.Array[1].Source, "2"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } // https://github.com/emicklei/proto/issues/99 func TestFieldCustomOptionLeadingDot(t *testing.T) { proto := `string app_entity_id = 4 [(.common.v1.some_custom_option) = { opt1: true opt2: false }];` p := newParserOn(proto) f := newNormalField() err := f.parse(p) if err != nil { t.Fatal(err) } if got, want := f.Type, "string"; got != want { t.Errorf("got [%v] want [%v]", got, want) } o := f.Options[0] if got, want := o.Name, "(.common.v1.some_custom_option)"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } // https://github.com/emicklei/proto/issues/106 func TestEmptyArrayInOptionStructure(t *testing.T) { src := ` option (grpc.gateway.protoc_gen_swagger.options.openapiv2_schema) = { json_schema : { title : "Frob a request" description : "blah blah blah" required : [ ] optional:["this"] } }; ` p := newParserOn(src) p.next() o := new(Option) if err := o.parse(p); err != nil { t.Fatal("testcase parse failed:", err) } s, ok := o.Constant.OrderedMap.Get("json_schema") if !ok { t.Fatal("expected json_schema literal") } // none a, ok := s.OrderedMap.Get("required") if !ok { t.Fatal("expected required literal") } if len(a.Array) != 0 { t.Fatal("expecting empty array") } // one a, ok = s.OrderedMap.Get("optional") if !ok { t.Fatal("expected required literal") } if len(a.Array) != 1 { t.Fatal("expecting one size array") } if got, want := a.Array[0].Source, "this"; got != want { t.Fatalf("got [%s] want [%s]", got, want) } } // https://github.com/emicklei/proto/issues/107 func TestQuoteNotDroppedInOption(t *testing.T) { src := `string name = 1 [ quote = '<="foo"' ];` f := newNormalField() if err := f.parse(newParserOn(src)); err != nil { t.Fatal(err) } sr := f.Options[0].Constant.SourceRepresentation() if got, want := sr, `'<="foo"'`; got != want { t.Errorf("got [%s] want [%s]", got, want) } } func TestWhatYouTypeIsWhatYouGetOptionValue(t *testing.T) { src := `string n = 1 [ quote = 'm"\"/"' ];` f := newNormalField() if err := f.parse(newParserOn(src)); err != nil { t.Fatal(err) } sr := f.Options[0].Constant.SourceRepresentation() if got, want := sr, `'m"\"/"'`; got != want { t.Errorf("got [%s] want [%s]", got, want) } } func TestLiteralNoQuoteRuneSet(t *testing.T) { l := Literal{ Source: "foo", IsString: true, } if got, want := l.SourceRepresentation(), "\"foo\""; got != want { t.Errorf("got [%s] want [%s]", got, want) } } func TestStringValuesParsedAsNumbers(t *testing.T) { src := `VAL0 = 0 [(enum_opt) = '09'];` f := new(EnumField) if err := f.parse(newParserOn(src)); err != nil { t.Fatal(err) } if got, want := f.ValueOption.Constant.Source, "09"; got != want { t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want) } } func TestCommentInsideArray(t *testing.T) { src := `option test = { scope_rules : [ // A comment // Another comment {has : [ // comment on test "test" ]} ] }; ` opt, err := newParserOn(src).Parse() if err != nil { t.Fatal(err) } opt2 := opt.Elements[0].(*Option) scp, _ := opt2.Constant.OrderedMap.Get("scope_rules") elem0 := scp.Array[0] t.Log("comment:", elem0.Comment.Lines) has, _ := elem0.OrderedMap.Get("has") if got, want := has.Array[0].Source, "test"; got != want { t.Errorf("got [%v:%T] want [%v:%T]", got, got, want, want) } t.Log("comment:", has.Array[0].Comment.Lines) } golang-github-emicklei-proto-1.14.0/package.go000066400000000000000000000036371473077502100212060ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "text/scanner" // Package specifies the namespace for all proto elements. type Package struct { Position scanner.Position Comment *Comment Name string InlineComment *Comment Parent Visitee } // Doc is part of Documented func (p *Package) Doc() *Comment { return p.Comment } func (p *Package) parse(pr *Parser) error { _, tok, lit := pr.nextIdent(true) if tIDENT != tok { if !isKeyword(tok) { return pr.unexpected(lit, "package identifier", p) } } p.Name = lit return nil } // Accept dispatches the call to the visitor. func (p *Package) Accept(v Visitor) { v.VisitPackage(p) } // inlineComment is part of commentInliner. func (p *Package) inlineComment(c *Comment) { p.InlineComment = c } func (p *Package) parent(v Visitee) { p.Parent = v } golang-github-emicklei-proto-1.14.0/package_test.go000066400000000000000000000026621473077502100222420ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "testing" ) func TestPackageParseWithReservedPrefix(t *testing.T) { want := "rpc.enum.oneof" ident := " " + want + ";" pkg := new(Package) p := newParserOn(ident) if err := pkg.parse(p); err != nil { t.Error(err) } if pkg.Name != want { t.Errorf("got %q want %q", pkg.Name, want) } } golang-github-emicklei-proto-1.14.0/parent_accessor.go000066400000000000000000000052431473077502100227610ustar00rootroot00000000000000// Copyright (c) 2018 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto func getParent(child Visitee) Visitee { if child == nil { return nil } pa := new(parentAccessor) child.Accept(pa) return pa.parent } type parentAccessor struct { parent Visitee } func (p *parentAccessor) VisitMessage(m *Message) { p.parent = m.Parent } func (p *parentAccessor) VisitService(v *Service) { p.parent = v.Parent } func (p *parentAccessor) VisitSyntax(s *Syntax) { p.parent = s.Parent } func (p *parentAccessor) VisitPackage(pkg *Package) { p.parent = pkg.Parent } func (p *parentAccessor) VisitOption(o *Option) { p.parent = o.Parent } func (p *parentAccessor) VisitImport(i *Import) { p.parent = i.Parent } func (p *parentAccessor) VisitNormalField(i *NormalField) { p.parent = i.Parent } func (p *parentAccessor) VisitEnumField(i *EnumField) { p.parent = i.Parent } func (p *parentAccessor) VisitEnum(e *Enum) { p.parent = e.Parent } func (p *parentAccessor) VisitComment(e *Comment) {} func (p *parentAccessor) VisitOneof(o *Oneof) { p.parent = o.Parent } func (p *parentAccessor) VisitOneofField(o *OneOfField) { p.parent = o.Parent } func (p *parentAccessor) VisitReserved(rs *Reserved) { p.parent = rs.Parent } func (p *parentAccessor) VisitRPC(rpc *RPC) { p.parent = rpc.Parent } func (p *parentAccessor) VisitMapField(f *MapField) { p.parent = f.Parent } func (p *parentAccessor) VisitGroup(g *Group) { p.parent = g.Parent } func (p *parentAccessor) VisitExtensions(e *Extensions) { p.parent = e.Parent } func (p *parentAccessor) VisitEdition(e *Edition) { p.parent = e.Parent } func (p *parentAccessor) VisitProto(*Proto) {} golang-github-emicklei-proto-1.14.0/parent_test.go000066400000000000000000000075071473077502100221430ustar00rootroot00000000000000// Copyright (c) 2018 Ernest Micklei // // # MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "fmt" "testing" ) type parentChecker struct { errors []error } func checkParent(v Visitee, t *testing.T) { pc := new(parentChecker) v.Accept(pc) if len(pc.errors) == 0 { return } for _, each := range pc.errors { t.Error(each) } } func (pc *parentChecker) checkAll(list []Visitee, parent Visitee) { for _, each := range list { if _, ok := each.(*Comment); ok { continue } if got, want := getParent(each), parent; got != want { pc.errors = append(pc.errors, fmt.Errorf("%T has wrong parent set, got %v want %v", each, got, want)) } each.Accept(pc) } } func (pc *parentChecker) check(astType, astName string, parent Visitee) { if parent == nil { pc.errors = append(pc.errors, fmt.Errorf("%s %s has no parent set", astType, astName)) } } func (pc *parentChecker) VisitProto(p *Proto) { pc.checkAll(p.Elements, p) } func (pc *parentChecker) VisitMessage(m *Message) { pc.check("Message", m.Name, m.Parent) pc.checkAll(m.Elements, m) } func (pc *parentChecker) VisitService(v *Service) { pc.check("Service", v.Name, v.Parent) pc.checkAll(v.Elements, v) } func (pc *parentChecker) VisitSyntax(s *Syntax) { pc.check("Syntax", s.Value, s.Parent) } func (pc *parentChecker) VisitPackage(p *Package) { pc.check("Package", p.Name, p.Parent) } func (pc *parentChecker) VisitOption(o *Option) { pc.check("Option", o.Name, o.Parent) } func (pc *parentChecker) VisitImport(i *Import) { pc.check("Import", i.Filename, i.Parent) } func (pc *parentChecker) VisitNormalField(i *NormalField) { pc.check("NormalField", i.Name, i.Parent) } func (pc *parentChecker) VisitEnumField(i *EnumField) { pc.check("EnumField", i.Name, i.Parent) } func (pc *parentChecker) VisitEnum(e *Enum) { pc.check("Enum", e.Name, e.Parent) pc.checkAll(e.Elements, e) } func (pc *parentChecker) VisitComment(e *Comment) {} func (pc *parentChecker) VisitOneof(o *Oneof) { pc.check("Oneof", o.Name, o.Parent) pc.checkAll(o.Elements, o) } func (pc *parentChecker) VisitOneofField(o *OneOfField) { pc.check("OneOfField", o.Name, o.Parent) } func (pc *parentChecker) VisitReserved(r *Reserved) { pc.check("Reserved", "", r.Parent) } func (pc *parentChecker) VisitRPC(r *RPC) { pc.check("RPC", r.Name, r.Parent) //pc.checkAll(r.Options, r) for _, each := range r.Options { pc.check("Option", each.Name, r) } } func (pc *parentChecker) VisitMapField(f *MapField) { pc.check("MapField", f.Name, f.Parent) } // proto2 func (pc *parentChecker) VisitGroup(g *Group) { pc.check("Group", g.Name, g.Parent) pc.checkAll(g.Elements, g) } func (pc *parentChecker) VisitExtensions(e *Extensions) { pc.check("Extensions", "", e.Parent) } // edition (proto3+) func (pc *parentChecker) VisitEdition(e *Edition) { pc.check("Edition", "", e.Parent) } golang-github-emicklei-proto-1.14.0/parser.go000066400000000000000000000175701473077502100211100ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "bytes" "errors" "fmt" "io" "runtime" "strconv" "strings" "text/scanner" ) // Parser represents a parser. type Parser struct { debug bool scanner *scanner.Scanner buf *nextValues scannerErrors []error } // nextValues is to capture the result of next() type nextValues struct { pos scanner.Position tok token lit string } // NewParser returns a new instance of Parser. func NewParser(r io.Reader) *Parser { s := new(scanner.Scanner) s.Init(r) s.Mode = scanner.ScanIdents | scanner.ScanFloats | scanner.ScanStrings | scanner.ScanRawStrings | scanner.ScanComments p := &Parser{scanner: s} s.Error = p.handleScanError return p } // handleScanError is called from the underlying Scanner func (p *Parser) handleScanError(s *scanner.Scanner, msg string) { p.scannerErrors = append(p.scannerErrors, fmt.Errorf("go scanner error at %v = %v", s.Position, msg)) } // ignoreIllegalEscapesWhile is called for scanning constants of an option. // Such content can have a syntax that is not acceptable by the Go scanner. // This temporary installs a handler that ignores only one type of error: illegal char escape func (p *Parser) ignoreIllegalEscapesWhile(block func()) { // during block call change error handler p.scanner.Error = func(s *scanner.Scanner, msg string) { // this catches both "illegal char escape" <= go1.12 and "invalid char escape" go1.13 if strings.Contains(msg, "char escape") { // too bad there is no constant for this in scanner pkg return } p.handleScanError(s, msg) } block() // restore p.scanner.Error = p.handleScanError } // Parse parses a proto definition. May return a parse or scanner error. func (p *Parser) Parse() (*Proto, error) { proto := new(Proto) if p.scanner.Filename != "" { proto.Filename = p.scanner.Filename } parseError := proto.parse(p) // see if it was a scanner error if len(p.scannerErrors) > 0 { buf := new(bytes.Buffer) for _, each := range p.scannerErrors { fmt.Fprintln(buf, each) } return proto, errors.New(buf.String()) } return proto, parseError } // Filename is for reporting. Optional. func (p *Parser) Filename(f string) { p.scanner.Filename = f } const stringWithSingleQuote = "'" // next returns the next token using the scanner or drain the buffer. func (p *Parser) next() (pos scanner.Position, tok token, lit string) { if p.buf != nil { // consume buf vals := *p.buf p.buf = nil return vals.pos, vals.tok, vals.lit } ch := p.scanner.Scan() if ch == scanner.EOF { return p.scanner.Position, tEOF, "" } lit = p.scanner.TokenText() // single quote needs additional scanning if stringWithSingleQuote == lit { return p.nextSingleQuotedString() } return p.scanner.Position, asToken(lit), lit } // pre: first single quote has been read func (p *Parser) nextSingleQuotedString() (pos scanner.Position, tok token, lit string) { var ch rune p.ignoreErrorsWhile(func() { ch = p.scanner.Scan() }) if ch == scanner.EOF { return p.scanner.Position, tEOF, "" } // string inside single quote lit = p.scanner.TokenText() if stringWithSingleQuote == lit { // empty single quoted string return p.scanner.Position, tIDENT, "''" } // scan for partial tokens until actual closing single-quote(') token for { p.ignoreErrorsWhile(func() { ch = p.scanner.Scan() }) if ch == scanner.EOF { return p.scanner.Position, tEOF, "" } partial := p.scanner.TokenText() if partial == "'" { break } lit += partial } // end quote expected if stringWithSingleQuote != p.scanner.TokenText() { p.unexpected(lit, "'", p) } return p.scanner.Position, tIDENT, fmt.Sprintf("'%s'", lit) } func (p *Parser) ignoreErrorsWhile(block func()) { // during block call change error handler which ignores it all p.scanner.Error = func(s *scanner.Scanner, msg string) { return } block() // restore p.scanner.Error = p.handleScanError } // nextPut sets the buffer func (p *Parser) nextPut(pos scanner.Position, tok token, lit string) { p.buf = &nextValues{pos, tok, lit} } func (p *Parser) unexpected(found, expected string, obj interface{}) error { debug := "" if p.debug { _, file, line, _ := runtime.Caller(1) debug = fmt.Sprintf(" at %s:%d (with %#v)", file, line, obj) } return fmt.Errorf("%v: found %q but expected [%s]%s", p.scanner.Position, found, expected, debug) } func (p *Parser) nextInteger() (i int, err error) { _, tok, lit := p.next() if "-" == lit { i, err = p.nextInteger() return i * -1, err } if tok != tNUMBER { return 0, errors.New("non integer") } if strings.HasPrefix(lit, "0x") || strings.HasPrefix(lit, "0X") { // hex decode i64, err := strconv.ParseInt(lit, 0, 64) return int(i64), err } i, err = strconv.Atoi(lit) return } // nextIdentifier consumes tokens which may have one or more dot separators (namespaced idents). func (p *Parser) nextIdentifier() (pos scanner.Position, tok token, lit string) { pos, tok, lit = p.nextIdent(false) if tDOT == tok { // leading dot allowed pos, tok, lit = p.nextIdent(false) lit = "." + lit } return } func (p *Parser) nextMessageLiteralFieldName() (pos scanner.Position, tok token, lit string) { pos, tok, lit = p.nextIdent(true) if tok == tLEFTSQUARE { pos, tok, lit = p.nextIdent(true) _, _, _ = p.next() // consume right square } return } // nextTypeName implements the Packages and Name Resolution for finding the name of the type. // Valid examples: // .google.protobuf.Empty // stream T must return tSTREAM // optional int32 must return tOPTIONAL // Bogus must return Bogus func (p *Parser) nextTypeName() (pos scanner.Position, tok token, lit string) { pos, tok, lit = p.next() startPos := pos fullLit := lit // leading dot allowed if tDOT == tok { pos, tok, lit = p.next() fullLit = fmt.Sprintf(".%s", lit) } // type can be namespaced more for { r := p.peekNonWhitespace() if '.' != r { break } p.next() // consume dot pos, tok, lit = p.next() fullLit = fmt.Sprintf("%s.%s", fullLit, lit) tok = tIDENT } return startPos, tok, fullLit } func (p *Parser) nextIdent(keywordStartAllowed bool) (pos scanner.Position, tok token, lit string) { pos, tok, lit = p.next() if tIDENT != tok { // can be keyword if !(isKeyword(tok) && keywordStartAllowed) { return } // proceed with keyword as first literal } startPos := pos fullLit := lit // see if identifier is namespaced for { r := p.peekNonWhitespace() if '.' != r { break } p.next() // consume dot pos, tok, lit := p.next() if tIDENT != tok && !isKeyword(tok) { p.nextPut(pos, tok, lit) break } fullLit = fmt.Sprintf("%s.%s", fullLit, lit) } return startPos, tIDENT, fullLit } func (p *Parser) peekNonWhitespace() rune { r := p.scanner.Peek() if r == scanner.EOF { return r } if isWhitespace(r) { // consume it p.scanner.Next() return p.peekNonWhitespace() } return r } golang-github-emicklei-proto-1.14.0/parser_test.go000066400000000000000000000137131473077502100221420ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "strings" "testing" ) func TestParseComment(t *testing.T) { proto := ` // first // second /* ctyle multi line */ // cpp style single line // message test{} ` p := newParserOn(proto) pr, err := p.Parse() if err != nil { t.Fatal(err) } if got, want := len(collect(pr).Comments()), 3; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func newParserOn(def string) *Parser { p := NewParser(strings.NewReader(def)) p.debug = true return p } func TestScanIgnoreWhitespace_Digits(t *testing.T) { p := newParserOn(" 1234 ") _, _, lit := p.next() if got, want := lit, "1234"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestScanIgnoreWhitespace_Minus(t *testing.T) { p := newParserOn(" -1234") _, _, lit := p.next() if got, want := lit, "-"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestNextIdentifier(t *testing.T) { ident := " aap.noot.mies " p := newParserOn(ident) _, tok, lit := p.nextIdentifier() if got, want := tok, tIDENT; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := lit, strings.TrimSpace(ident); got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestNextIdentifierWithKeyword(t *testing.T) { ident := " aap.rpc.mies.enum =" p := newParserOn(ident) _, tok, lit := p.nextIdentifier() if got, want := tok, tIDENT; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := lit, "aap.rpc.mies.enum"; got != want { t.Errorf("got [%v] want [%v]", got, want) } _, tok, _ = p.next() if got, want := tok, tEQUALS; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestNextTypeNameWithLeadingKeyword(t *testing.T) { ident := " service.me.now" p := newParserOn(ident) _, tok, lit := p.nextTypeName() if got, want := tok, tIDENT; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := lit, "service.me.now"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestNextIdentifierNoIdent(t *testing.T) { ident := "(" p := newParserOn(ident) _, tok, lit := p.nextIdentifier() if got, want := tok, tLEFTPAREN; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := lit, "("; got != want { t.Errorf("got [%v] want [%v]", got, want) } } // https://github.com/google/protobuf/issues/4726 func TestProtobufIssue4726(t *testing.T) { src := `syntax = "proto3"; service SomeService { rpc SomeMethod (Whatever) returns (Whatever) { option (google.api.http) = { delete : "/some/url" additional_bindings { delete: "/another/url" } }; } }` p := newParserOn(src) _, err := p.Parse() if err != nil { t.Error(err) } } func TestProtoIssue92(t *testing.T) { src := `syntax = "proto3"; package test; message Foo { .game.Resource one = 1 [deprecated = true]; repeated .game.sub.Resource two = 2; map three = 3; }` p := newParserOn(src) _, err := p.Parse() if err != nil { t.Error(err) } } func TestParseSingleQuotesStrings(t *testing.T) { p := newParserOn(` 'bohemian','' `) _, _, lit := p.next() if got, want := lit, "'bohemian'"; got != want { t.Errorf("got [%v] want [%v]", got, want) } _, tok, _ := p.next() if got, want := tok, tCOMMA; got != want { t.Errorf("got [%v] want [%v]", got, want) } _, _, lit = p.next() if got, want := lit, "''"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestProtoIssue132(t *testing.T) { src := `syntax = "proto3"; package tutorial; message Person { string name = 1; int32 id = 0x2; // Unique ID number for this person. string email = 0X3; // parser.Parse err :8:18: found "=" but expected [field sequence number] }` p := newParserOn(src) _, err := p.Parse() if err != nil { t.Error(err) } } func TestReservedNegativeRanges(t *testing.T) { r := new(Reserved) p := newParserOn(`reserved -1;`) _, tok, _ := p.next() if tRESERVED != tok { t.Fail() } err := r.parse(p) if err != nil { t.Fatal(err) } if got, want := r.Ranges[0].SourceRepresentation(), "-1"; got != want { t.Fatalf("got [%v] want [%v]", got, want) // reserved_test.go:59: got [1] want [-1] } } func TestParseNegativeEnum(t *testing.T) { const def = ` syntax = "proto3"; package example; enum Value { ZERO = 0; reserved -2, -1; }` p := NewParser(strings.NewReader(def)) _, err := p.Parse() if err != nil { t.Fatal(err) // :7:16: found "-" but expected [range integer] } } func TestParseInfMessage(t *testing.T) { const def = ` message Inf { string field = 1; } message NaN { string field = 1; } message Infinity { string field = 1; } message ExampelMessage { Inf inf_field = 1; NaN nan_field = 2; Infinity infinity_field = 3; } ` p := NewParser(strings.NewReader(def)) _, err := p.Parse() if err != nil { t.Fatal(err) // :7:16: found "-" but expected [range integer] } } golang-github-emicklei-proto-1.14.0/proto.go000066400000000000000000000116751473077502100207570ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto // Proto represents a .proto definition type Proto struct { Filename string Elements []Visitee } // Accept dispatches the call to the visitor. func (proto *Proto) Accept(v Visitor) { // As Proto is not (yet) a Visitee, we enumerate its elements instead //v.VisitProto(proto) for _, each := range proto.Elements { each.Accept(v) } } // addElement is part of elementContainer func (proto *Proto) addElement(v Visitee) { v.parent(proto) proto.Elements = append(proto.Elements, v) } // elements is part of elementContainer func (proto *Proto) elements() []Visitee { return proto.Elements } // takeLastComment is part of elementContainer // removes and returns the last element of the list if it is a Comment. func (proto *Proto) takeLastComment(expectedOnLine int) (last *Comment) { last, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, expectedOnLine) return } // parse parsers a complete .proto definition source. func (proto *Proto) parse(p *Parser) error { for { pos, tok, lit := p.next() switch { case isComment(lit): if com := mergeOrReturnComment(proto.Elements, lit, pos); com != nil { // not merged? proto.Elements = append(proto.Elements, com) } case tOPTION == tok: o := new(Option) o.Position = pos o.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) if err := o.parse(p); err != nil { return err } proto.addElement(o) case tSYNTAX == tok: s := new(Syntax) s.Position = pos s.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) if err := s.parse(p); err != nil { return err } proto.addElement(s) case tEDITION == tok: s := new(Edition) s.Position = pos s.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) if err := s.parse(p); err != nil { return err } proto.addElement(s) case tIMPORT == tok: im := new(Import) im.Position = pos im.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) if err := im.parse(p); err != nil { return err } proto.addElement(im) case tENUM == tok: enum := new(Enum) enum.Position = pos enum.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) if err := enum.parse(p); err != nil { return err } proto.addElement(enum) case tSERVICE == tok: service := new(Service) service.Position = pos service.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) err := service.parse(p) if err != nil { return err } proto.addElement(service) case tPACKAGE == tok: pkg := new(Package) pkg.Position = pos pkg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) if err := pkg.parse(p); err != nil { return err } proto.addElement(pkg) case tMESSAGE == tok: msg := new(Message) msg.Position = pos msg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) if err := msg.parse(p); err != nil { return err } proto.addElement(msg) // BEGIN proto2 case tEXTEND == tok: msg := new(Message) msg.Position = pos msg.Comment, proto.Elements = takeLastCommentIfEndsOnLine(proto.Elements, pos.Line-1) msg.IsExtend = true if err := msg.parse(p); err != nil { return err } proto.addElement(msg) // END proto2 case tSEMICOLON == tok: maybeScanInlineComment(p, proto) // continue case tEOF == tok: goto done default: return p.unexpected(lit, ".proto element {comment|option|import|syntax|enum|service|package|message}", p) } } done: return nil } func (proto *Proto) parent(v Visitee) {} // elementContainer unifies types that have elements. type elementContainer interface { addElement(v Visitee) elements() []Visitee takeLastComment(expectedOnLine int) *Comment } golang-github-emicklei-proto-1.14.0/protobuf_test.go000066400000000000000000000017371473077502100225110ustar00rootroot00000000000000package proto import ( "net/http" "os" "testing" ) func fetchAndParse(t *testing.T, url string) *Proto { resp, err := http.Get(url) if err != nil { t.Fatal(url, err) } defer resp.Body.Close() parser := NewParser(resp.Body) def, err := parser.Parse() if err != nil { t.Fatal(url, err) } t.Log("elements:", len(def.Elements)) return def } // PB=y go test -v -run ^TestPublicProtoDefinitions$ func TestPublicProtoDefinitions(t *testing.T) { if len(os.Getenv("PB")) == 0 { t.Skip("PB test not run") } for _, each := range []string{ "https://raw.githubusercontent.com/gogo/protobuf/master/test/thetest.proto", "https://raw.githubusercontent.com/gogo/protobuf/master/test/theproto3/theproto3.proto", "https://raw.githubusercontent.com/googleapis/googleapis/master/google/privacy/dlp/v2/dlp.proto", // "https://raw.githubusercontent.com/envoyproxy/data-plane-api/master/envoy/api/v2/auth/cert.proto", } { def := fetchAndParse(t, each) checkParent(def, t) } } golang-github-emicklei-proto-1.14.0/range.go000066400000000000000000000054031473077502100207000ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "fmt" "strconv" ) // Range is to specify number intervals (with special end value "max") type Range struct { From, To int Max bool } // SourceRepresentation return a single number if from = to. Returns to otherwise unless Max then return to max. func (r Range) SourceRepresentation() string { if r.Max { return fmt.Sprintf("%d to max", r.From) } if r.From == r.To { return strconv.Itoa(r.From) } return fmt.Sprintf("%d to %d", r.From, r.To) } // parseRanges is used to parse ranges for extensions and reserved func parseRanges(p *Parser, n Visitee) (list []Range, err error) { seenTo := false negate := false // for numbers for { pos, tok, lit := p.next() if isString(lit) { return list, p.unexpected(lit, "integer, ", n) } switch lit { case "-": negate = true case ",": case "to": seenTo = true case ";": p.nextPut(pos, tok, lit) // allow for inline comment parsing goto done case "max": if !seenTo { return list, p.unexpected(lit, "to", n) } from := list[len(list)-1] list = append(list[0:len(list)-1], Range{From: from.From, Max: true}) default: // must be number i, err := strconv.Atoi(lit) if err != nil { return list, p.unexpected(lit, "range integer", n) } if negate { i = -i negate = false } if seenTo { // replace last two ranges with one if len(list) < 1 { p.unexpected(lit, "integer", n) } from := list[len(list)-1] list = append(list[0:len(list)-1], Range{From: from.From, To: i}) seenTo = false } else { list = append(list, Range{From: i, To: i}) } } } done: return } golang-github-emicklei-proto-1.14.0/range_test.go000066400000000000000000000043031473077502100217350ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestParseRanges(t *testing.T) { r := new(Reserved) p := newParserOn(`reserved 2, 15, 9 to 11;`) _, _, _ = p.next() ranges, err := parseRanges(p, r) if err != nil { t.Fatal(err) } if got, want := ranges[2].SourceRepresentation(), "9 to 11"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestParseRangesMax(t *testing.T) { r := new(Extensions) p := newParserOn(`extensions 3 to max;`) _, _, _ = p.next() ranges, err := parseRanges(p, r) if err != nil { t.Fatal(err) } if got, want := ranges[0].SourceRepresentation(), "3 to max"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestParseRangesMultiToMax(t *testing.T) { r := new(Extensions) p := newParserOn(`extensions 1,2 to 5,6 to 9,10 to max;`) _, _, _ = p.next() ranges, err := parseRanges(p, r) if err != nil { t.Fatal(err) } if got, want := len(ranges), 4; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := ranges[3].SourceRepresentation(), "10 to max"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } golang-github-emicklei-proto-1.14.0/reserved.go000066400000000000000000000044771473077502100214350ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "text/scanner" // Reserved statements declare a range of field numbers or field names that cannot be used in a message. type Reserved struct { Position scanner.Position Comment *Comment Ranges []Range FieldNames []string InlineComment *Comment Parent Visitee } // inlineComment is part of commentInliner. func (r *Reserved) inlineComment(c *Comment) { r.InlineComment = c } // Accept dispatches the call to the visitor. func (r *Reserved) Accept(v Visitor) { v.VisitReserved(r) } func (r *Reserved) parse(p *Parser) error { for { pos, tok, lit := p.next() if len(lit) == 0 { return p.unexpected(lit, "reserved string or integer", r) } // first char that determined tok ch := []rune(lit)[0] if isDigit(ch) || ch == '-' { // use unread here because it could be start of ranges p.nextPut(pos, tok, lit) list, err := parseRanges(p, r) if err != nil { return err } r.Ranges = list continue } if isString(lit) { s, _ := unQuote(lit) r.FieldNames = append(r.FieldNames, s) continue } if tSEMICOLON == tok { p.nextPut(pos, tok, lit) break } } return nil } func (r *Reserved) parent(v Visitee) { r.Parent = v } golang-github-emicklei-proto-1.14.0/reserved_test.go000066400000000000000000000041121473077502100224560ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestReservedRanges(t *testing.T) { r := new(Reserved) p := newParserOn(`reserved 2, 15, 9 to 11;`) _, tok, _ := p.next() if tRESERVED != tok { t.Fail() } err := r.parse(p) if err != nil { t.Fatal(err) } if got, want := r.Ranges[0].SourceRepresentation(), "2"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := r.Ranges[2].SourceRepresentation(), "9 to 11"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestReservedFieldNames(t *testing.T) { r := new(Reserved) p := newParserOn(`reserved "foo", "bar";`) _, _, _ = p.next() err := r.parse(p) if err != nil { t.Fatal(err) } if got, want := len(r.FieldNames), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := r.FieldNames[0], "foo"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := r.FieldNames[1], "bar"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } golang-github-emicklei-proto-1.14.0/service.go000066400000000000000000000142661473077502100212530ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Service defines a set of RPC calls. type Service struct { Position scanner.Position Comment *Comment Name string Elements []Visitee Parent Visitee } // Accept dispatches the call to the visitor. func (s *Service) Accept(v Visitor) { v.VisitService(s) } // Doc is part of Documented func (s *Service) Doc() *Comment { return s.Comment } // addElement is part of elementContainer func (s *Service) addElement(v Visitee) { v.parent(s) s.Elements = append(s.Elements, v) } // elements is part of elementContainer func (s *Service) elements() []Visitee { return s.Elements } // takeLastComment is part of elementContainer // removes and returns the last elements of the list if it is a Comment. func (s *Service) takeLastComment(expectedOnLine int) (last *Comment) { last, s.Elements = takeLastCommentIfEndsOnLine(s.Elements, expectedOnLine) return } // parse continues after reading "service" func (s *Service) parse(p *Parser) error { pos, tok, lit := p.nextIdentifier() if tok != tIDENT { if !isKeyword(tok) { return p.unexpected(lit, "service identifier", s) } } s.Name = lit consumeCommentFor(p, s) pos, tok, lit = p.next() if tok != tLEFTCURLY { return p.unexpected(lit, "service opening {", s) } for { pos, tok, lit = p.next() switch tok { case tCOMMENT: if com := mergeOrReturnComment(s.Elements, lit, pos); com != nil { // not merged? s.addElement(com) } case tOPTION: opt := new(Option) opt.Position = pos opt.Comment, s.Elements = takeLastCommentIfEndsOnLine(s.elements(), pos.Line-1) if err := opt.parse(p); err != nil { return err } s.addElement(opt) case tRPC: rpc := new(RPC) rpc.Position = pos rpc.Comment, s.Elements = takeLastCommentIfEndsOnLine(s.Elements, pos.Line-1) err := rpc.parse(p) if err != nil { return err } s.addElement(rpc) maybeScanInlineComment(p, s) case tSEMICOLON: maybeScanInlineComment(p, s) case tRIGHTCURLY: goto done default: return p.unexpected(lit, "service comment|rpc", s) } } done: return nil } func (s *Service) parent(v Visitee) { s.Parent = v } // RPC represents an rpc entry in a message. type RPC struct { Position scanner.Position Comment *Comment Name string RequestType string StreamsRequest bool ReturnsType string StreamsReturns bool Elements []Visitee InlineComment *Comment Parent Visitee // Options field is DEPRECATED, use Elements instead. Options []*Option } // Accept dispatches the call to the visitor. func (r *RPC) Accept(v Visitor) { v.VisitRPC(r) } // Doc is part of Documented func (r *RPC) Doc() *Comment { return r.Comment } // inlineComment is part of commentInliner. func (r *RPC) inlineComment(c *Comment) { r.InlineComment = c } // parse continues after reading "rpc" func (r *RPC) parse(p *Parser) error { pos, tok, lit := p.next() if tok != tIDENT { return p.unexpected(lit, "rpc method", r) } r.Name = lit pos, tok, lit = p.next() if tok != tLEFTPAREN { return p.unexpected(lit, "rpc type opening (", r) } pos, tok, lit = p.nextTypeName() if tSTREAM == tok { r.StreamsRequest = true pos, tok, lit = p.nextTypeName() } if tok != tIDENT { return p.unexpected(lit, "rpc stream | request type", r) } r.RequestType = lit pos, tok, lit = p.next() if tok != tRIGHTPAREN { return p.unexpected(lit, "rpc type closing )", r) } pos, tok, lit = p.next() if tok != tRETURNS { return p.unexpected(lit, "rpc returns", r) } pos, tok, lit = p.next() if tok != tLEFTPAREN { return p.unexpected(lit, "rpc type opening (", r) } pos, tok, lit = p.nextTypeName() if tSTREAM == tok { r.StreamsReturns = true pos, tok, lit = p.nextTypeName() } if tok != tIDENT { return p.unexpected(lit, "rpc stream | returns type", r) } r.ReturnsType = lit pos, tok, lit = p.next() if tok != tRIGHTPAREN { return p.unexpected(lit, "rpc type closing )", r) } pos, tok, lit = p.next() if tSEMICOLON == tok { p.nextPut(pos, tok, lit) // allow for inline comment parsing return nil } if tLEFTCURLY == tok { // parse options for { pos, tok, lit = p.next() if tRIGHTCURLY == tok { break } if isComment(lit) { if com := mergeOrReturnComment(r.elements(), lit, pos); com != nil { // not merged? r.addElement(com) continue } } if tSEMICOLON == tok { maybeScanInlineComment(p, r) continue } if tOPTION == tok { o := new(Option) o.Position = pos if err := o.parse(p); err != nil { return err } r.addElement(o) } } } return nil } // addElement is part of elementContainer func (r *RPC) addElement(v Visitee) { v.parent(r) r.Elements = append(r.Elements, v) // handle deprecated field if option, ok := v.(*Option); ok { r.Options = append(r.Options, option) } } // elements is part of elementContainer func (r *RPC) elements() []Visitee { return r.Elements } func (r *RPC) takeLastComment(expectedOnLine int) (last *Comment) { last, r.Elements = takeLastCommentIfEndsOnLine(r.Elements, expectedOnLine) return } func (r *RPC) parent(v Visitee) { r.Parent = v } golang-github-emicklei-proto-1.14.0/service_test.go000066400000000000000000000143221473077502100223030ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestService(t *testing.T) { proto := `service AccountService { // comment rpc CreateAccount (CreateAccount) returns (ServiceFault); // inline comment rpc GetAccounts (stream Int64) returns (Account) {} // inline comment2 rpc Health(google.protobuf.Empty) returns (google.protobuf.Empty) {} // inline comment3 }` pr, err := newParserOn(proto).Parse() if err != nil { t.Fatal(err) } srv := collect(pr).Services()[0] if got, want := len(srv.Elements), 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := srv.Position.String(), ":1:1"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } rpc1 := srv.Elements[0].(*RPC) if got, want := rpc1.Name, "CreateAccount"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := rpc1.Doc().Message(), " comment"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := rpc1.InlineComment.Message(), " inline comment"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := rpc1.Position.Line, 3; got != want { t.Fatalf("got [%v] want [%v]", got, want) } rpc2 := srv.Elements[1].(*RPC) if got, want := rpc2.Name, "GetAccounts"; got != want { t.Errorf("got [%v] want [%v]", got, want) } rpc3 := srv.Elements[2].(*RPC) if got, want := rpc3.Name, "Health"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if rpc2.InlineComment == nil { t.Fatal("missing inline comment 2") } if got, want := rpc2.InlineComment.Message(), " inline comment2"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if rpc3.InlineComment == nil { t.Fatal("missing inline comment 3") } if got, want := rpc3.InlineComment.Message(), " inline comment3"; got != want { t.Fatalf("got [%v] want [%v]", got, want) } } func TestRPCWithOptionAggregateSyntax(t *testing.T) { proto := `service AccountService { // CreateAccount rpc CreateAccount (CreateAccount) returns (ServiceFault){ // test_ident option (test_ident) = { test: "test" test2:"test2" }; // inline test_ident } }` pr, err := newParserOn(proto).Parse() if err != nil { t.Fatal(err) } srv := collect(pr).Services()[0] if got, want := len(srv.Elements), 1; got != want { t.Fatalf("got [%v] want [%v]", got, want) } rpc1 := srv.Elements[0].(*RPC) if got, want := len(rpc1.Elements), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } com := rpc1.Elements[0].(*Comment) if got, want := com.Message(), " test_ident"; got != want { t.Errorf("got [%v] want [%v]", got, want) } opt := rpc1.Elements[1].(*Option) if got, want := opt.Name, "(test_ident)"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := opt.InlineComment != nil, true; got != want { t.Fatalf("got [%v] want [%v]", got, want) } if got, want := opt.InlineComment.Message(), " inline test_ident"; got != want { t.Errorf("got [%v] want [%v]", got, want) } if got, want := len(opt.Constant.Map), 2; got != want { t.Fatalf("got [%v] want [%v]", got, want) } // test deprecated field Options in RPC if got, want := len(rpc1.Options), 1; got != want { t.Errorf("got len Options %v want %v", got, want) } } func TestServiceWithOption(t *testing.T) { src := `service AnyService { option secure = true; }` p := newParserOn(src) p.next() svc := new(Service) err := svc.parse(p) if err != nil { t.Fatal(err) } if got, want := svc.Elements[0].(*Option).Name, "secure"; got != want { t.Errorf("got [%v] want [%v]", got, want) } checkParent(svc.Elements[0].(*Option), t) } func TestRPCWithOneLineCommentInOptionBlock(t *testing.T) { proto := `service AccountService { rpc CreateAccount (CreateAccount) returns (ServiceFault) { // test comment } }` _, err := newParserOn(proto).Parse() if err != nil { t.Fatal(err) } } func TestRPCWithMultiLineCommentInOptionBlock(t *testing.T) { proto := `service AccountService { rpc CreateAccount (CreateAccount) returns (ServiceFault) { // test comment // test comment } }` def, err := newParserOn(proto).Parse() if err != nil { t.Fatal(err) } s := def.Elements[0].(*Service) r := s.Elements[0].(*RPC) if got, want := len(r.Elements), 1; got != want { t.Errorf("got [%v] want [%v]", got, want) } c := r.Elements[0].(*Comment) if got, want := len(c.Lines), 2; got != want { t.Errorf("got [%v] want [%v]", got, want) } } func TestRPCWithTypeThatHasLeadingDot(t *testing.T) { src := `service Dummy { rpc DeleteProgram (ProgramIdentifier) returns (.google.protobuf.Empty) {} }` _, err := newParserOn(src).Parse() if err != nil { t.Fatal(err) } } func TestServiceInlineCommentBeforeBody(t *testing.T) { src := `service BarService // BarService // with another line { rpc Foo (Void) returns (Magic) // FooRPC { // yet another line } } ` p := newParserOn(src) svc := new(Service) p.next() if err := svc.parse(p); err != nil { t.Fatal(err) } nestedComment := svc.Elements[0].(*Comment) if nestedComment == nil { t.Fatal("expected comment present") } if got, want := len(nestedComment.Lines), 2; got != want { t.Errorf("got %d want %d lines", got, want) } } golang-github-emicklei-proto-1.14.0/syntax.go000066400000000000000000000037111473077502100211320ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "text/scanner" ) // Syntax should have value "proto" type Syntax struct { Position scanner.Position Comment *Comment Value string InlineComment *Comment Parent Visitee } func (s *Syntax) parse(p *Parser) error { if _, tok, lit := p.next(); tok != tEQUALS { return p.unexpected(lit, "syntax =", s) } _, _, lit := p.next() if !isString(lit) { return p.unexpected(lit, "syntax string constant", s) } s.Value, _ = unQuote(lit) return nil } // Accept dispatches the call to the visitor. func (s *Syntax) Accept(v Visitor) { v.VisitSyntax(s) } // Doc is part of Documented func (s *Syntax) Doc() *Comment { return s.Comment } // inlineComment is part of commentInliner. func (s *Syntax) inlineComment(c *Comment) { s.InlineComment = c } func (s *Syntax) parent(v Visitee) { s.Parent = v } golang-github-emicklei-proto-1.14.0/syntax_test.go000066400000000000000000000026611473077502100221740ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestSyntax(t *testing.T) { proto := `syntax = "proto";` p := newParserOn(proto) p.next() // consume first token s := new(Syntax) err := s.parse(p) if err != nil { t.Fatal(err) } if got, want := s.Value, "proto"; got != want { t.Errorf("got [%v] want [%v]", got, want) } } golang-github-emicklei-proto-1.14.0/token.go000066400000000000000000000122501473077502100207220ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import ( "strconv" "strings" ) // token represents a lexical token. type token int const ( // Special tokens tILLEGAL token = iota tEOF tWS // Literals tIDENT // Misc characters tSEMICOLON // ; tCOLON // : tEQUALS // = tQUOTE // " tSINGLEQUOTE // ' tLEFTPAREN // ( tRIGHTPAREN // ) tLEFTCURLY // { tRIGHTCURLY // } tLEFTSQUARE // [ tRIGHTSQUARE // ] tCOMMENT // / tLESS // < tGREATER // > tCOMMA // , tDOT // . // Keywords keywordsStart tEDITION tSYNTAX tSERVICE tRPC tRETURNS tMESSAGE tIMPORT tPACKAGE tOPTION tREPEATED tWEAK tPUBLIC // special fields tONEOF tMAP tRESERVED tENUM tSTREAM // numbers (pos or neg, float) tNUMBER // BEGIN proto2 tOPTIONAL tGROUP tEXTENSIONS tEXTEND tREQUIRED // END proto2 keywordsEnd ) // typeTokens exists for future validation const typeTokens = "double float int32 int64 uint32 uint64 sint32 sint64 fixed32 sfixed32 sfixed64 bool string bytes" // isKeyword returns if tok is in the keywords range func isKeyword(tok token) bool { return keywordsStart < tok && tok < keywordsEnd } // isWhitespace checks for space,tab and newline func isWhitespace(r rune) bool { return r == ' ' || r == '\t' || r == '\n' } // isDigit returns true if the rune is a digit. func isDigit(ch rune) bool { return (ch >= '0' && ch <= '9') } // isString checks if the literal is quoted (single or double). func isString(lit string) bool { if lit == "'" { return false } return (strings.HasPrefix(lit, "\"") && strings.HasSuffix(lit, "\"")) || (strings.HasPrefix(lit, "'") && strings.HasSuffix(lit, "'")) } func isComment(lit string) bool { return strings.HasPrefix(lit, "//") || strings.HasPrefix(lit, "/*") } func isNumber(lit string) bool { if lit == "NaN" || lit == "nan" || lit == "Inf" || lit == "Infinity" || lit == "inf" || lit == "infinity" { return false } if strings.HasPrefix(lit, "0x") || strings.HasPrefix(lit, "0X") { _, err := strconv.ParseInt(lit, 0, 64) return err == nil } _, err := strconv.ParseFloat(lit, 64) return err == nil } const doubleQuoteRune = rune('"') // unQuote removes one matching leading and trailing single or double quote. // // https://github.com/emicklei/proto/issues/103 // cannot use strconv.Unquote as this unescapes quotes. func unQuote(lit string) (string, rune) { if len(lit) < 2 { return lit, doubleQuoteRune } chars := []rune(lit) first, last := chars[0], chars[len(chars)-1] if first != last { return lit, doubleQuoteRune } if s := string(chars[0]); s == "\"" || s == stringWithSingleQuote { return string(chars[1 : len(chars)-1]), chars[0] } return lit, doubleQuoteRune } func asToken(literal string) token { switch literal { // delimiters case ";": return tSEMICOLON case ":": return tCOLON case "=": return tEQUALS case "\"": return tQUOTE case "'": return tSINGLEQUOTE case "(": return tLEFTPAREN case ")": return tRIGHTPAREN case "{": return tLEFTCURLY case "}": return tRIGHTCURLY case "[": return tLEFTSQUARE case "]": return tRIGHTSQUARE case "<": return tLESS case ">": return tGREATER case ",": return tCOMMA case ".": return tDOT // words case "syntax": return tSYNTAX case "edition": return tEDITION case "service": return tSERVICE case "rpc": return tRPC case "returns": return tRETURNS case "option": return tOPTION case "message": return tMESSAGE case "import": return tIMPORT case "package": return tPACKAGE case "oneof": return tONEOF // special fields case "map": return tMAP case "reserved": return tRESERVED case "enum": return tENUM case "repeated": return tREPEATED case "weak": return tWEAK case "public": return tPUBLIC case "stream": return tSTREAM // proto2 case "optional": return tOPTIONAL case "group": return tGROUP case "extensions": return tEXTENSIONS case "extend": return tEXTEND case "required": return tREQUIRED default: // special cases if isNumber(literal) { return tNUMBER } if isComment(literal) { return tCOMMENT } return tIDENT } } golang-github-emicklei-proto-1.14.0/token_test.go000066400000000000000000000045361473077502100217710ustar00rootroot00000000000000// Copyright (c) 2019 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto import "testing" func TestUnQuoteCases(t *testing.T) { singleQuoteRune := rune('\'') for i, each := range []struct { input, output string quoteRune rune }{ {"thanos", "thanos", doubleQuoteRune}, {"`bucky`", "`bucky`", doubleQuoteRune}, {"'nat", "'nat", doubleQuoteRune}, {"'bruce'", "bruce", singleQuoteRune}, {"\"tony\"", "tony", doubleQuoteRune}, {"\"'\"\"' -> \"\"\"\"\"\"", `'""' -> """""`, doubleQuoteRune}, {`"''"`, "''", doubleQuoteRune}, {"''", "", singleQuoteRune}, {"", "", doubleQuoteRune}, } { got, gotRune := unQuote(each.input) if gotRune != each.quoteRune { t.Errorf("[%d] got [%v] want [%v]", i, gotRune, each.quoteRune) } want := each.output if got != want { t.Errorf("[%d] got [%s] want [%s]", i, got, want) } } } func TestIsNumber(t *testing.T) { for i, each := range []struct { input string isNumber bool }{ {`1`, true}, {`1.2`, true}, {`-1.02`, true}, {`a1`, false}, {`0x12`, true}, {`0X77777`, true}, {`NaN`, false}, {`nan`, false}, {`Inf`, false}, {`Infinity`, false}, {`inf`, false}, {`infinity`, false}, } { got := isNumber(each.input) if got != each.isNumber { t.Errorf("[%d] got [%v] want [%v]", i, got, each.isNumber) } } } golang-github-emicklei-proto-1.14.0/visitor.go000066400000000000000000000036621473077502100213100ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto // Visitor is for dispatching Proto elements. type Visitor interface { VisitMessage(m *Message) VisitService(v *Service) VisitSyntax(s *Syntax) VisitPackage(p *Package) VisitOption(o *Option) VisitImport(i *Import) VisitNormalField(i *NormalField) VisitEnumField(i *EnumField) VisitEnum(e *Enum) VisitComment(e *Comment) VisitOneof(o *Oneof) VisitOneofField(o *OneOfField) VisitReserved(r *Reserved) VisitRPC(r *RPC) VisitMapField(f *MapField) // proto2 VisitGroup(g *Group) VisitExtensions(e *Extensions) // edition (proto3+), v2 // VisitEdition(e *Edition) } // Visitee is implemented by all Proto elements. type Visitee interface { Accept(v Visitor) parent(e Visitee) } // Documented is for types that may have an associated comment (not inlined). type Documented interface { Doc() *Comment } golang-github-emicklei-proto-1.14.0/visitor_test.go000066400000000000000000000036071473077502100223460ustar00rootroot00000000000000// Copyright (c) 2017 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto type collector struct { proto *Proto } func collect(p *Proto) collector { return collector{p} } func (c collector) Comments() (list []*Comment) { for _, each := range c.proto.Elements { if c, ok := each.(*Comment); ok { list = append(list, c) } } return } func (c collector) Enums() (list []*Enum) { for _, each := range c.proto.Elements { if c, ok := each.(*Enum); ok { list = append(list, c) } } return } func (c collector) Messages() (list []*Message) { for _, each := range c.proto.Elements { if c, ok := each.(*Message); ok { list = append(list, c) } } return } func (c collector) Services() (list []*Service) { for _, each := range c.proto.Elements { if c, ok := each.(*Service); ok { list = append(list, c) } } return } golang-github-emicklei-proto-1.14.0/walk.go000066400000000000000000000071601473077502100205440ustar00rootroot00000000000000// Copyright (c) 2018 Ernest Micklei // // MIT License // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. package proto // Handler is a type of function that accepts a Visitee. type Handler func(v Visitee) // Walk recursively pays a visit to all Visitees of a Proto and calls each handler with it. func Walk(proto *Proto, handlers ...Handler) { walk(proto, handlers...) } func walk(container elementContainer, handlers ...Handler) { for _, eachElement := range container.elements() { for _, eachFilter := range handlers { eachFilter(eachElement) } if next, ok := eachElement.(elementContainer); ok { walk(next, handlers...) } } } // WithImport returns a Handler that will call the apply function when the Visitee is an Import. func WithImport(apply func(*Import)) Handler { return func(v Visitee) { if s, ok := v.(*Import); ok { apply(s) } } } // WithMessage returns a Handler that will call the apply function when the Visitee is a Message. func WithMessage(apply func(*Message)) Handler { return func(v Visitee) { if s, ok := v.(*Message); ok { apply(s) } } } // WithOption returns a Handler that will call the apply function when the Visitee is a Option. func WithOption(apply func(*Option)) Handler { return func(v Visitee) { if s, ok := v.(*Option); ok { apply(s) } } } // WithEnum returns a Handler that will call the apply function when the Visitee is a Enum. func WithEnum(apply func(*Enum)) Handler { return func(v Visitee) { if s, ok := v.(*Enum); ok { apply(s) } } } // WithOneof returns a Handler that will call the apply function when the Visitee is a Oneof. func WithOneof(apply func(*Oneof)) Handler { return func(v Visitee) { if s, ok := v.(*Oneof); ok { apply(s) } } } // WithService returns a Handler that will call the apply function when the Visitee is a Service. func WithService(apply func(*Service)) Handler { return func(v Visitee) { if s, ok := v.(*Service); ok { apply(s) } } } // WithRPC returns a Handler that will call the apply function when the Visitee is a RPC. func WithRPC(apply func(*RPC)) Handler { return func(v Visitee) { if s, ok := v.(*RPC); ok { apply(s) } } } // WithPackage returns a Handler that will call the apply function when the Visitee is a Package. func WithPackage(apply func(*Package)) Handler { return func(v Visitee) { if s, ok := v.(*Package); ok { apply(s) } } } // WithNormalField returns a Handler that will call the apply function when the Visitee is a NormalField. func WithNormalField(apply func(*NormalField)) Handler { return func(v Visitee) { if s, ok := v.(*NormalField); ok { apply(s) } } } golang-github-emicklei-proto-1.14.0/walk_test.go000066400000000000000000000017241473077502100216030ustar00rootroot00000000000000package proto import ( "os" "testing" ) type counter struct { counts map[string]int } func (c counter) handleService(s *Service) { c.counts["service"] = c.counts["service"] + 1 } func (c counter) handleRPC(r *RPC) { c.counts["rpc"] = c.counts["rpc"] + 1 } func (c counter) handleImport(r *Import) { c.counts["import"] = c.counts["import"] + 1 } func (c counter) handleNormalField(r *NormalField) { c.counts["normal field"] = c.counts["import"] + 1 } func TestWalkGoogleApisDLP(t *testing.T) { if len(os.Getenv("PB")) == 0 { t.Skip("PB test not run") } proto := fetchAndParse(t, "https://raw.githubusercontent.com/gogo/protobuf/master/test/theproto3/theproto3.proto") count := counter{counts: map[string]int{}} Walk(proto, WithPackage(func(p *Package) { t.Log("package:", p.Name) }), WithService(count.handleService), WithRPC(count.handleRPC), WithImport(count.handleImport), WithNormalField(count.handleNormalField), ) t.Logf("%#v", count) }