wangming1993 / issues

记录学习中的一些问题,体会与心得 https://wangming1993.github.io/issues
8 stars 4 forks source link

golang检测PHP中特定语句输出MD #50

Open wangming1993 opened 7 years ago

wangming1993 commented 7 years ago
package main

import (
    "bufio"
    "encoding/json"
    "flag"
    "fmt"
    "os"
    "path/filepath"
    "regexp"
    "strings"
)

var lookup = flag.String("lookup", "", "The root path")
var writen = flag.Bool("w", false, "Whether to write to file")
var help = flag.Bool("h", false, "List command")

var reporter *Reporter

func init() {
    flag.Parse()
    reporter = &Reporter{
        modules: make(map[string]*module),
    }
}

func main() {
    if *help || *lookup == "" {
        usage()
        return
    }

    filepath.Walk(*lookup, osWalk)
    var content string
    content = reporter.Markdown()
    if *writen {
        file, err := os.Create("output.md")
        if err != nil {
            fmt.Println(err)
            return
        }

        file.WriteString(content)
    } else {
        os.Stdout.WriteString(content)
    }

}

func usage() {
    cmd := `
find replaced rpc code in specified directory:

-lookup     The root directory of php code
-w          Write to file if ture, default output in console
-h          The usage
`
    fmt.Println(cmd)
}

func osWalk(path string, info os.FileInfo, err error) error {
    if err != nil {
        fmt.Println("------------------------------")
        fmt.Println(err)
        fmt.Println("------------------------------")
    }
    if info.IsDir() || !isController(path) {
        return nil
    }
    controller := initializeController(path, info.Name())
    addController(controller)
    return nil
}

func isController(file string) bool {
    controllerFolder := strings.Contains(file, "controllers")
    if !controllerFolder {
        return false
    }

    return strings.HasSuffix(file, "Controller.php")
}

func initializeController(file, name string) *controller {
    lines := readFile(file)
    controller := &controller{
        name:   name,
        file:   file,
        lines:  lines,
        module: getModuleName(file),
    }
    max := len(lines)
    i := 0
    for {
        if i >= max {
            break
        }
        line := lines[i]
        if isAction(line) {
            action, step := controller.findAction(i, getActionName(line))
            if action != nil {
                controller.actions = append(controller.actions, action)
            }
            i = step
        } else {
            i++
        }
        serviceUseName := findServiceUseName(line)
        if serviceUseName != "" {
            controller.serviceUserName = append(controller.serviceUserName, serviceUseName)
        }
    }
    return controller
}

func isAction(line string) bool {
    pattern, _ := regexp.Compile("^\\s*public\\s+function\\s+action")
    return pattern.MatchString(line)
}

func getActionName(line string) string {
    pattern := "^\\s*public\\s+function\\s+action([A-Za-z]+)\\s?"
    c, _ := regexp.Compile(pattern)
    matches := c.FindStringSubmatch(line)
    if len(matches) < 2 {
        return ""
    }
    return matches[1]
}

func readFile(file string) []string {
    f, err := os.Open(file)
    if err != nil {
        panic(err)
    }
    defer f.Close()

    var lines []string
    scanner := bufio.NewScanner(f)
    for scanner.Scan() {
        lines = append(lines, scanner.Text())

    }
    if err := scanner.Err(); err != nil {
        fmt.Fprintln(os.Stderr, err)
    }

    return lines
}

type action struct {
    controller  *controller
    name        string
    fullName    string
    start       int
    end         int
    needMigrate bool
}

func (a *action) hasRpcMigrate() bool {
    i := a.start
    for {
        if i >= a.end {
            return false
        }
        line := a.controller.lines[i]
        if hasServiceCall(line, a.controller.serviceUserName) {
            return true
        }
        i++
    }
    return false
}

type controller struct {
    file            string
    module          string
    name            string
    lines           []string
    actions         []*action
    serviceUserName []string
}

func (c *controller) getMigratedActionNames() []string {
    var actionNames []string
    for _, a := range c.actions {
        if a.hasRpcMigrate() {
            actionNames = append(actionNames, a.fullName)
        }
    }
    return actionNames
}

func (c *controller) findAction(start int, name string) (*action, int) {
    max := len(c.lines)
    i := start
    var leftBrace, rightBrace int

    for {
        if i >= max {
            return nil, i
        }
        line := c.lines[i]
        leftBrace += strings.Count(line, "{")
        rightBrace += strings.Count(line, "}")

        if leftBrace > 0 && leftBrace == rightBrace {
            action := &action{
                controller: c,
                name:       name,
                fullName:   fmt.Sprintf("action%s", name),
                start:      start,
                end:        i,
            }
            return action, i
        }
        i++
    }
    return nil, i
}

func findServiceUseName(line string) string {
    pattern := "^\\s*use\\s+backend\\\\modules\\\\[a-zA-Z]+\\\\services\\\\([a-zA-Z]+){1}(\\s?as\\s*([A-Za-z]+))?"
    c, _ := regexp.Compile(pattern)
    matches := c.FindStringSubmatch(line)
    if len(matches) < 2 {
        return ""
    }

    useName := matches[1]
    max := len(matches)
    if matches[max-1] != "" {
        useName = matches[max-1]
    }
    return useName
}

func hasServiceCall(line string, serviceName []string) bool {
    var contains bool
    for _, service := range serviceName {
        contains = contains || strings.Contains(line, service)
    }
    return contains
}

func getModuleName(path string) string {
    pattern := "backend/modules/([A-Za-z]+)/controllers"
    c, _ := regexp.Compile(pattern)
    matches := c.FindStringSubmatch(path)
    if len(matches) < 2 {
        return ""
    }

    return matches[1]
}

func toJson(v interface{}) string {
    buf, err := json.MarshalIndent(v, "", "\t")
    if err != nil {
        panic(err)
    }
    return string(buf)
}

type module struct {
    name        string
    controllers []*controller
}

type Reporter struct {
    modules map[string]*module
}

func (r *Reporter) Markdown() string {
    var md string
    for _, m := range r.modules {
        var hasMigratedCode bool
        moduleMD := fmt.Sprintf("\n# %s \n", m.name)
        for _, c := range m.controllers {
            actionNames := c.getMigratedActionNames()
            if len(actionNames) > 0 {
                hasMigratedCode = true
                moduleMD += fmt.Sprintf("\n## %s \n", c.name)
                moduleMD += fmt.Sprintf("\n```php\n%s\n```\n", toJson(actionNames))
            }
        }
        if hasMigratedCode {
            md += moduleMD
        }
    }

    return md
}

func addController(c *controller) {
    if module, ok := reporter.modules[c.module]; ok {
        module.controllers = append(module.controllers, c)
        return
    }
    m := &module{
        name:        c.module,
        controllers: []*controller{c},
    }
    reporter.modules[c.module] = m
}