-
Notifications
You must be signed in to change notification settings - Fork 2
/
php-linter.go
222 lines (181 loc) · 5.16 KB
/
php-linter.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
package main
import (
"flag"
"fmt"
"github.com/fatih/color"
"io/ioutil"
"log"
"os"
"os/exec"
"strings"
"sync"
"sync/atomic"
)
// php-linter-go 一个高性能 PHP 语法检测工具
// 用 Golang 给 PHP 做嫁衣的感觉真不错
// by Rytia <admin@zzfly.net>, 2021-07-17 周六
var (
processLintWg sync.WaitGroup
lintErrResult sync.Map
totalFileCounter uint32
errFileCounter uint32
)
// PHP 可执行文件路径
var phpExecutor = "php"
// 控制最大运行协程个数
var ch = make(chan bool, 8)
// 进程状态码
var (
StatusCodeExceptionOccurred = 255
StatusCoedFilesError = 254
)
func main() {
// 获取运行路径
wd, _ := os.Getwd()
isRecursive := flag.Bool("recursive", false, "是否递归执行")
isGit := flag.Bool("git", false, "检测 Git 中的变更")
isSvn := flag.Bool("svn", false, "检测 SVN 中的变更")
inputPath := flag.String("path", wd, "检测地址")
inputPhpExecutor := flag.String("php-executor", "php", "PHP 解释器可运行文件地址")
flag.Parse()
phpExecutor = *inputPhpExecutor
printWelcome()
if *isGit {
lintGit()
} else if *isSvn {
lintSvn()
} else {
if inputPath != nil {
lintPath(*inputPath, *isRecursive)
} else {
color.HiRed("Unknown error for getting path")
}
}
processLintWg.Wait()
printResult()
if errFileCounter > 0 {
os.Exit(StatusCoedFilesError)
}
os.Exit(0)
}
func printWelcome() {
getPhpVersionCmd := phpExecutor + " -r\"echo PHP_VERSION;\""
stdout, err := execCommand(getPhpVersionCmd)
if err != nil {
color.HiRed("PHP execute error. Pleas ensure that php is installed and \"-php-executor\" parameter is correct")
os.Exit(StatusCodeExceptionOccurred)
}
fmt.Printf("\nSimple PHP Linter (PHP %s) \n\n", stdout)
}
func printResult() {
fmt.Println("\n---------------------------")
fmt.Println("Result:")
fmt.Printf("Check: %d / Errors: %d \n", totalFileCounter, errFileCounter)
if errFileCounter > 0 {
lintErrResult.Range(processResultPrint)
}
fmt.Println()
}
// 获取执行目录下的文件,执行检测
func lintPath(path string, recursive bool) {
// 获取文件
dir, err := ioutil.ReadDir(path)
if err != nil {
log.Fatalln(err)
}
// 逐个检查
for _, file := range dir {
if recursive && file.IsDir() {
lintPath(path+"/"+file.Name(), recursive)
} else if strings.Contains(file.Name(), ".php") {
processLintWg.Add(1)
go processPhpLint(path, file.Name())
}
}
}
// 获取 Git 文件差异,执行检测
func lintGit() {
getGitRootPathCmd := "git rev-parse --show-toplevel"
gitRootPath, err := execCommand(getGitRootPathCmd)
if err != nil {
color.HiRed("Git is not installed or work directory is not a git repository")
os.Exit(StatusCodeExceptionOccurred)
}
gitRootPath = strings.Trim(gitRootPath, "\n")
fmt.Printf("Git Root:\t%s\n", gitRootPath)
getGitBranchCmd := "git symbolic-ref --short -q HEAD"
gitBranch, err := execCommand(getGitBranchCmd)
if err != nil {
color.HiRed("Git branch error")
}
fmt.Printf("Git Branch:\t%s\n", gitBranch)
getGitDiffFilesCmd := "git diff origin/master --name-only|grep .php"
gitDiffFileString, _ := execCommand(getGitDiffFilesCmd)
gitDiffFiles := strings.Split(gitDiffFileString, "\n")
for _, file := range gitDiffFiles {
// 防止空串影响
if len(file) != 0 {
processLintWg.Add(1)
go processPhpLint(gitRootPath, file)
}
}
}
// 获取 SVN 文件差异,执行检测
func lintSvn() {
getSvnUrlCmd := "svn info --show-item url"
svnUrl, err := execCommand(getSvnUrlCmd)
if err != nil {
color.HiRed("SVN is not installed or work directory is not a svn repository")
os.Exit(StatusCodeExceptionOccurred)
}
svnUrl = strings.Trim(svnUrl, "\n")
fmt.Printf("SVN Url:\t%s\n", svnUrl)
svnPath, _ := os.Getwd()
fmt.Printf("SVN Path:\t%s\n\n", svnPath)
getSvnDiffFilesCmd := "svn diff --summarize|grep .php|awk '{print $2}'"
svnDiffFileString, _ := execCommand(getSvnDiffFilesCmd)
svnDiffFiles := strings.Split(svnDiffFileString, "\n")
for _, file := range svnDiffFiles {
// 防止空串影响
if len(file) != 0 {
processLintWg.Add(1)
go processPhpLint(svnPath, file)
}
}
}
// 处理 PHP 语法检测
func processPhpLint(path string, file string) {
defer processLintWg.Done()
// 控制最大协程数
ch <- true
// 拼接文件真实路径
realFilePath := file
if len(path) != 0 {
realFilePath = path + "/" + file
}
lintPhpCmd := phpExecutor + " -l " + realFilePath
stdout, err := execCommand(lintPhpCmd)
if err != nil {
fmt.Println(color.HiRedString("[ERR]"), file, err.Error())
lintErrResult.Store(path+"/"+file, stdout)
atomic.AddUint32(&errFileCounter, 1)
} else {
fmt.Println(color.HiGreenString("[OK] "), file)
}
atomic.AddUint32(&totalFileCounter, 1)
<-ch
}
// 遍历输出检测结果
func processResultPrint(key, value interface{}) bool {
file := strings.Trim(fmt.Sprintf("%v", key), " ")
fileString := color.CyanString("[%s]", file)
infoString := strings.Trim(fmt.Sprintf("%v", value), "\n")
fmt.Printf("\n%s\n%s\n", fileString, infoString)
return true
}
// 简易执行 shell 命令
func execCommand(command string) (string, error) {
cmd := exec.Command("/bin/sh", "-c", command)
stdout, err := cmd.Output()
return string(stdout), err
}