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:
219
tools/goctl/api/parser/apifileparser.go
Normal file
219
tools/goctl/api/parser/apifileparser.go
Normal 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
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user