refactor parser and remove deprecated code (#204)

* add comment support

* add comment support

* 1. group support multi level folder
2. remove force flag

* bug fix

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

* refactor parser and remove deprecated code

Co-authored-by: kim <xutao@xiaoheiban.cn>
This commit is contained in:
kingxt
2020-11-13 23:01:19 +08:00
committed by GitHub
parent ef4d4968d6
commit 16bfb1b7be
14 changed files with 272 additions and 123 deletions

View File

@@ -0,0 +1,219 @@
package parser
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"strings"
)
const (
tokenInfo = "info"
tokenImport = "import"
tokenType = "type"
tokenService = "service"
tokenServiceAnnotation = "@server"
)
type (
ApiStruct struct {
Info string
Type string
Service string
Imports string
serviceBeginLine int
}
apiFileState interface {
process(api *ApiStruct, token string) (apiFileState, error)
}
apiRootState struct {
*baseState
}
apiInfoState struct {
*baseState
}
apiImportState struct {
*baseState
}
apiTypeState struct {
*baseState
}
apiServiceState struct {
*baseState
}
)
func ParseApi(src string) (*ApiStruct, error) {
var buffer = new(bytes.Buffer)
buffer.WriteString(src)
api := new(ApiStruct)
var lineNumber = api.serviceBeginLine
apiFile := baseState{r: bufio.NewReader(buffer), lineNumber: &lineNumber}
st := apiRootState{&apiFile}
for {
st, err := st.process(api, "")
if err == io.EOF {
return api, nil
}
if err != nil {
return nil, fmt.Errorf("near line: %d, %s", lineNumber, err.Error())
}
if st == nil {
return api, nil
}
}
}
func (s *apiRootState) process(api *ApiStruct, token string) (apiFileState, error) {
var builder strings.Builder
for {
ch, err := s.readSkipComment()
if err != nil {
return nil, err
}
switch {
case isSpace(ch) || isNewline(ch) || ch == leftParenthesis:
token := builder.String()
token = strings.TrimSpace(token)
if len(token) == 0 {
continue
}
builder.Reset()
switch token {
case tokenInfo:
info := apiInfoState{s.baseState}
return info.process(api, token+string(ch))
case tokenImport:
tp := apiImportState{s.baseState}
return tp.process(api, token+string(ch))
case tokenType:
ty := apiTypeState{s.baseState}
return ty.process(api, token+string(ch))
case tokenService:
server := apiServiceState{s.baseState}
return server.process(api, token+string(ch))
case tokenServiceAnnotation:
server := apiServiceState{s.baseState}
return server.process(api, token+string(ch))
default:
if strings.HasPrefix(token, "//") {
continue
}
return nil, errors.New(fmt.Sprintf("invalid token %s at line %d", token, *s.lineNumber))
}
default:
builder.WriteRune(ch)
}
}
}
func (s *apiInfoState) process(api *ApiStruct, token string) (apiFileState, error) {
for {
line, err := s.readLine()
if err != nil {
return nil, err
}
api.Info += "\n" + token + line
token = ""
if strings.TrimSpace(line) == string(rightParenthesis) {
return &apiRootState{s.baseState}, nil
}
}
}
func (s *apiImportState) process(api *ApiStruct, token string) (apiFileState, error) {
line, err := s.readLine()
if err != nil {
return nil, err
}
line = token + line
if len(strings.Fields(line)) != 2 {
return nil, errors.New("import syntax error: " + line)
}
api.Imports += "\n" + line
return &apiRootState{s.baseState}, nil
}
func (s *apiTypeState) process(api *ApiStruct, token string) (apiFileState, error) {
var blockCount = 0
for {
line, err := s.readLine()
if err != nil {
return nil, err
}
api.Type += "\n\n" + token + line
token = ""
line = strings.TrimSpace(line)
line = removeComment(line)
if strings.HasSuffix(line, leftBrace) {
blockCount++
}
if strings.HasSuffix(line, string(leftParenthesis)) {
blockCount++
}
if strings.HasSuffix(line, string(rightBrace)) {
blockCount--
}
if strings.HasSuffix(line, string(rightParenthesis)) {
blockCount--
}
if blockCount == 0 {
return &apiRootState{s.baseState}, nil
}
}
}
func (s *apiServiceState) process(api *ApiStruct, token string) (apiFileState, error) {
var blockCount = 0
for {
line, err := s.readLineSkipComment()
if err != nil {
return nil, err
}
line = token + line
token = ""
api.Service += "\n" + line
line = strings.TrimSpace(line)
line = removeComment(line)
if strings.HasSuffix(line, leftBrace) {
blockCount++
}
if strings.HasSuffix(line, string(leftParenthesis)) {
blockCount++
}
if line == string(rightBrace) {
blockCount--
}
if line == string(rightParenthesis) {
blockCount--
}
if blockCount == 0 {
return &apiRootState{s.baseState}, nil
}
}
}
func removeComment(line string) string {
var commentIdx = strings.Index(line, "//")
if commentIdx >= 0 {
return line[:commentIdx]
}
return line
}

View File

@@ -3,6 +3,7 @@ package parser
import (
"bufio"
"bytes"
"errors"
"fmt"
"io"
"io/ioutil"
@@ -34,10 +35,11 @@ func NewParser(filename string) (*Parser, error) {
if err != nil {
return nil, err
}
for _, item := range strings.Split(apiStruct.Imports, "\n") {
ip := strings.TrimSpace(item)
if len(ip) > 0 {
item := strings.TrimPrefix(item, "import")
importLine := strings.TrimSpace(item)
if len(importLine) > 0 {
item := strings.TrimPrefix(importLine, "import")
item = strings.TrimSpace(item)
item = strings.TrimPrefix(item, `"`)
item = strings.TrimSuffix(item, `"`)
@@ -46,18 +48,33 @@ func NewParser(filename string) (*Parser, error) {
path = filepath.Join(filepath.Dir(apiAbsPath), item)
}
content, err := ioutil.ReadFile(path)
if err != nil {
return nil, errors.New("import api file not exist: " + item)
}
importStruct, err := ParseApi(string(content))
if err != nil {
return nil, err
}
apiStruct.StructBody += "\n" + string(content)
if len(importStruct.Imports) > 0 {
return nil, errors.New("import api should not import another api file recursive")
}
apiStruct.Type += "\n" + importStruct.Type
apiStruct.Service += "\n" + importStruct.Service
}
}
if len(strings.TrimSpace(apiStruct.Service)) == 0 {
return nil, errors.New("api has no service defined")
}
var buffer = new(bytes.Buffer)
buffer.WriteString(apiStruct.Service)
return &Parser{
r: bufio.NewReader(buffer),
typeDef: apiStruct.StructBody,
typeDef: apiStruct.Type,
api: apiStruct,
}, nil
}
@@ -69,6 +86,7 @@ func (p *Parser) Parse() (api *spec.ApiSpec, err error) {
if err != nil {
return nil, err
}
api.Types = types
var lineNumber = p.api.serviceBeginLine
st := newRootState(p.r, &lineNumber)

View File

@@ -40,9 +40,7 @@ func (s *serviceState) process(api *spec.ApiSpec) (state, error) {
}
api.Service = spec.Service{
Name: name,
Annotations: append(api.Service.Annotations, s.annos...),
Routes: append(api.Service.Routes, routes...),
Name: name,
Groups: append(api.Service.Groups, spec.Group{
Annotations: s.annos,
Routes: routes,

View File

@@ -2,22 +2,12 @@ package parser
import (
"bufio"
"errors"
"strings"
"github.com/tal-tech/go-zero/tools/goctl/api/spec"
)
var emptyType spec.Type
type ApiStruct struct {
Info string
StructBody string
Service string
Imports string
serviceBeginLine int
}
func GetType(api *spec.ApiSpec, t string) spec.Type {
for _, tp := range api.Types {
if tp.Name == t {
@@ -73,82 +63,3 @@ func skipSpaces(r *bufio.Reader) error {
func unread(r *bufio.Reader) error {
return r.UnreadRune()
}
func ParseApi(api string) (*ApiStruct, error) {
var result ApiStruct
scanner := bufio.NewScanner(strings.NewReader(api))
var parseInfo = false
var parseImport = false
var parseType = false
var parseService = false
var segment string
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if line == "info(" {
parseInfo = true
}
if line == ")" && parseInfo {
parseInfo = false
result.Info = segment + ")"
segment = ""
continue
}
if isImportBeginLine(line) {
parseImport = true
}
if parseImport && (isTypeBeginLine(line) || isServiceBeginLine(line)) {
parseImport = false
result.Imports = segment
segment = line + "\n"
continue
}
if isTypeBeginLine(line) {
parseType = true
}
if isServiceBeginLine(line) {
parseService = true
if parseType {
parseType = false
result.StructBody = segment
segment = line + "\n"
continue
}
}
segment += scanner.Text() + "\n"
}
if !parseService {
return nil, errors.New("no service defined")
}
result.Service = segment
result.serviceBeginLine = lineBeginOfService(api)
return &result, nil
}
func isImportBeginLine(line string) bool {
return strings.HasPrefix(line, "import") && (strings.HasSuffix(line, ".api") || strings.HasSuffix(line, `.api"`))
}
func isTypeBeginLine(line string) bool {
return strings.HasPrefix(line, "type")
}
func isServiceBeginLine(line string) bool {
return strings.HasPrefix(line, "@server") || (strings.HasPrefix(line, "service") && strings.HasSuffix(line, "{"))
}
func lineBeginOfService(api string) int {
scanner := bufio.NewScanner(strings.NewReader(api))
var number = 0
for scanner.Scan() {
line := strings.TrimSpace(scanner.Text())
if isServiceBeginLine(line) {
break
}
number++
}
return number
}

View File

@@ -40,7 +40,7 @@ func (p *Parser) validateDuplicateProperty(tp spec.Type) (bool, string) {
func (p *Parser) validateDuplicateRouteHandler(api *spec.ApiSpec) (bool, string) {
var names []string
for _, r := range api.Service.Routes {
for _, r := range api.Service.Routes() {
handler, ok := util.GetAnnotationValue(r.Annotations, "server", "handler")
if !ok {
return false, fmt.Sprintf("missing handler annotation for %s", r.Path)