mirror of https://github.com/etcd-io/protodoc.git
213 lines
5.1 KiB
Go
213 lines
5.1 KiB
Go
// Copyright 2016 CoreOS, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package parse
|
|
|
|
import (
|
|
"bufio"
|
|
"io"
|
|
"os"
|
|
"strings"
|
|
)
|
|
|
|
func readLines(r io.Reader) ([]string, error) {
|
|
var (
|
|
lines []string
|
|
scanner = bufio.NewScanner(r)
|
|
)
|
|
for scanner.Scan() {
|
|
// remove indents
|
|
line := strings.TrimSpace(scanner.Text())
|
|
if len(line) > 0 {
|
|
if strings.HasPrefix(line, "//") {
|
|
lines = append(lines, line)
|
|
continue
|
|
}
|
|
|
|
// remove semi-colon line-separator
|
|
sl := strings.Split(line, ";")
|
|
for _, txt := range sl {
|
|
if len(txt) > 0 {
|
|
if strings.HasPrefix(txt, "optional ") { // proto2
|
|
txt = strings.Replace(txt, "optional ", "", 1)
|
|
}
|
|
lines = append(lines, txt)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if err := scanner.Err(); err != nil {
|
|
return nil, err
|
|
}
|
|
return lines, nil
|
|
}
|
|
|
|
type parseMode int
|
|
|
|
const (
|
|
reading parseMode = iota
|
|
parsingMessage
|
|
parsingService
|
|
parsingRPC
|
|
)
|
|
|
|
func ReadDir(targetDir string) (*Proto, error) {
|
|
rm, err := walkDirExt(targetDir, ".proto")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var lines []string
|
|
for _, fpath := range rm {
|
|
f, err := os.OpenFile(fpath, os.O_RDONLY, 0444)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ls, err := readLines(f)
|
|
if err != nil {
|
|
f.Close()
|
|
return nil, err
|
|
}
|
|
lines = append(lines, ls...)
|
|
|
|
f.Close()
|
|
}
|
|
|
|
var (
|
|
rp = Proto{
|
|
Messages: []ProtoMessage{},
|
|
Services: []ProtoService{},
|
|
}
|
|
mode = reading
|
|
|
|
comments []string
|
|
protoMessage = ProtoMessage{}
|
|
protoService = ProtoService{}
|
|
)
|
|
|
|
skippingEnum := false
|
|
for _, line := range lines {
|
|
if strings.HasPrefix(line, "//") {
|
|
ls := strings.Replace(line, "//", "", 1)
|
|
comments = append(comments, strings.TrimSpace(ls))
|
|
continue
|
|
}
|
|
if strings.HasPrefix(line, "enum ") {
|
|
skippingEnum = true
|
|
continue
|
|
}
|
|
if skippingEnum {
|
|
if strings.HasSuffix(line, "}") { // end of enum
|
|
skippingEnum = false
|
|
}
|
|
continue
|
|
}
|
|
|
|
switch mode {
|
|
case reading:
|
|
for j, elem := range strings.Fields(line) {
|
|
switch j {
|
|
case 0:
|
|
switch elem {
|
|
case "message":
|
|
mode = parsingMessage
|
|
|
|
case "service":
|
|
mode = parsingService
|
|
}
|
|
|
|
case 1: // proto message/service name
|
|
switch mode {
|
|
case parsingMessage: // message Name
|
|
protoMessage.Name = strings.Replace(elem, "{", "", -1)
|
|
protoMessage.Description = strings.Join(comments, " ")
|
|
comments = []string{} // reset
|
|
protoMessage.Fields = []ProtoField{} // reset
|
|
|
|
case parsingService: // service Name
|
|
protoService.Name = strings.Replace(elem, "{", "", -1)
|
|
protoService.Description = strings.Join(comments, " ")
|
|
comments = []string{} // reset
|
|
protoService.Methods = []ProtoMethod{} // reset
|
|
}
|
|
}
|
|
}
|
|
|
|
case parsingMessage:
|
|
if strings.HasSuffix(line, "}") { // closing of message
|
|
rp.Messages = append(rp.Messages, protoMessage)
|
|
protoMessage = ProtoMessage{}
|
|
comments = []string{}
|
|
mode = reading
|
|
continue
|
|
}
|
|
|
|
protoField := ProtoField{}
|
|
tl := line
|
|
if strings.HasPrefix(tl, "repeated") {
|
|
protoField.Repeated = true
|
|
tl = strings.Replace(tl, "repeated ", "", -1)
|
|
}
|
|
fds := strings.Fields(tl)
|
|
tp, err := ToProtoType(fds[0])
|
|
if err != nil {
|
|
protoField.ProtoType = 0
|
|
protoField.UserDefinedProtoType = fds[0]
|
|
} else {
|
|
protoField.ProtoType = tp
|
|
protoField.UserDefinedProtoType = ""
|
|
}
|
|
|
|
protoField.Name = fds[1]
|
|
protoField.Description = strings.Join(comments, " ")
|
|
protoMessage.Fields = append(protoMessage.Fields, protoField)
|
|
comments = []string{}
|
|
|
|
case parsingService:
|
|
// parse 'rpc Watch(stream WatchRequest) returns (stream WatchResponse) {}'
|
|
if strings.HasPrefix(line, "rpc ") {
|
|
lt := strings.Replace(line, "rpc ", "", 1)
|
|
lt = strings.Replace(lt, ")", "", -1)
|
|
lt = strings.Replace(lt, " {}", "", 1)
|
|
fsigs := strings.Split(lt, " returns ")
|
|
|
|
ft := strings.Split(fsigs[0], "(") // split 'Watch(stream WatchRequest'
|
|
f1 := ft[0]
|
|
|
|
ft = strings.Fields(ft[1])
|
|
f2 := ft[len(ft)-1]
|
|
|
|
ft = strings.Fields(strings.Replace(fsigs[1], "(", "", 1)) // split '(stream WatchResponse'
|
|
f3 := ft[len(ft)-1]
|
|
|
|
protoMethod := ProtoMethod{} // reset
|
|
protoMethod.Name = f1
|
|
protoMethod.RequestType = f2
|
|
protoMethod.ResponseType = f3
|
|
protoMethod.Description = strings.Join(comments, " ")
|
|
protoService.Methods = append(protoService.Methods, protoMethod)
|
|
comments = []string{}
|
|
} else if !strings.HasSuffix(line, "{}") && strings.HasSuffix(line, "}") { // closing of service
|
|
rp.Services = append(rp.Services, protoService)
|
|
protoService = ProtoService{}
|
|
comments = []string{}
|
|
mode = reading
|
|
}
|
|
}
|
|
}
|
|
|
|
return &rp, nil
|
|
}
|