mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-09-15 22:10:26 +00:00
371 lines
7.5 KiB
Go
371 lines
7.5 KiB
Go
package main
|
|
|
|
import (
|
|
"bytes"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"runtime"
|
|
"sort"
|
|
"strings"
|
|
"sync"
|
|
"sync/atomic"
|
|
|
|
"mvdan.cc/gofumpt/format"
|
|
)
|
|
|
|
var (
|
|
directory = flag.String("pwd", "", "Working directory of Xray vformat.")
|
|
action = flag.String("mode", "format", "Execution mode. Default is 'format'.\n'format' formatting source files and save changes to files.\n'check' list all paths of improper formatted file.\n'dryrun' formatting source files and shows all diffs, but will not make any changes to files.")
|
|
)
|
|
|
|
var (
|
|
isCheck bool
|
|
isDryrun bool
|
|
isFormat bool
|
|
)
|
|
|
|
func getModuleInfo(pwd string) (modPath, langVersion string, err error) {
|
|
data, err := os.ReadFile(filepath.Join(pwd, "go.mod"))
|
|
if err != nil {
|
|
return "", "", err
|
|
}
|
|
for _, line := range strings.Split(string(data), "\n") {
|
|
fields := strings.Fields(line)
|
|
if len(fields) >= 2 {
|
|
switch fields[0] {
|
|
case "module":
|
|
modPath = fields[1]
|
|
case "go":
|
|
langVersion = "go" + strings.TrimPrefix(fields[1], "go")
|
|
}
|
|
}
|
|
}
|
|
return modPath, langVersion, nil
|
|
}
|
|
|
|
func formatGoSource(src []byte, opts format.Options) ([]byte, error) {
|
|
return format.Source(src, opts)
|
|
}
|
|
|
|
func main() {
|
|
flag.Usage = func() {
|
|
fmt.Fprintf(flag.CommandLine.Output(), "Usage of vformat:\n")
|
|
flag.PrintDefaults()
|
|
}
|
|
flag.Parse()
|
|
|
|
if !filepath.IsAbs(*directory) {
|
|
pwd, wdErr := os.Getwd()
|
|
if wdErr != nil {
|
|
fmt.Println("Can not get current working directory.")
|
|
os.Exit(1)
|
|
}
|
|
*directory = filepath.Join(pwd, *directory)
|
|
}
|
|
|
|
switch *action {
|
|
case "format":
|
|
isFormat = true
|
|
case "check":
|
|
isCheck = true
|
|
case "dryrun":
|
|
isCheck = true
|
|
isDryrun = true
|
|
default:
|
|
fmt.Println("Unrecognized 'mode'. Will format all source files and save changes.")
|
|
isFormat = true
|
|
}
|
|
|
|
pwd := *directory
|
|
modPath, langVersion, modErr := getModuleInfo(pwd)
|
|
if modErr != nil {
|
|
fmt.Println("Error reading go.mod:", modErr)
|
|
os.Exit(1)
|
|
}
|
|
opts := format.Options{
|
|
LangVersion: langVersion,
|
|
ModulePath: modPath,
|
|
}
|
|
|
|
if isFormat {
|
|
fmt.Println("Formatting Go source files...")
|
|
} else if isCheck {
|
|
fmt.Println("Checking files thar are not properly formatted...")
|
|
}
|
|
|
|
jobs := make(chan string, runtime.NumCPU())
|
|
var wg sync.WaitGroup
|
|
var formatRequired atomic.Bool
|
|
var hasErrors atomic.Bool
|
|
|
|
for i := 0; i < runtime.NumCPU(); i++ {
|
|
wg.Go(func() {
|
|
for path := range jobs {
|
|
src, err := os.ReadFile(path)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error reading %s: %v\n", path, err)
|
|
hasErrors.Store(true)
|
|
continue
|
|
}
|
|
|
|
formatted, err := formatGoSource(src, opts)
|
|
if err != nil {
|
|
fmt.Fprintf(os.Stderr, "Error formatting %s: %v\n", path, err)
|
|
hasErrors.Store(true)
|
|
continue
|
|
}
|
|
|
|
if !bytes.Equal(src, formatted) {
|
|
var diffText []byte
|
|
if isDryrun {
|
|
newName := filepath.ToSlash(path)
|
|
oldName := newName + ".orig"
|
|
diffText = diff(oldName, src, newName, formatted)
|
|
}
|
|
if isFormat {
|
|
info, statErr := os.Stat(path)
|
|
if statErr != nil {
|
|
fmt.Fprintf(os.Stderr, "Error stating %s: %v\n", path, statErr)
|
|
hasErrors.Store(true)
|
|
continue
|
|
}
|
|
if writeErr := os.WriteFile(path, formatted, info.Mode().Perm()); writeErr != nil {
|
|
fmt.Fprintf(os.Stderr, "Error writing %s: %v\n", path, writeErr)
|
|
hasErrors.Store(true)
|
|
continue
|
|
}
|
|
}
|
|
|
|
formatRequired.Store(true)
|
|
if isDryrun && len(diffText) > 0 {
|
|
fmt.Printf("%s\n%s", path, diffText)
|
|
} else {
|
|
fmt.Println(path)
|
|
}
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
walkErr := filepath.Walk(pwd, func(path string, info os.FileInfo, err error) error {
|
|
if err != nil {
|
|
fmt.Println(err)
|
|
return err
|
|
}
|
|
|
|
if info.IsDir() {
|
|
return nil
|
|
}
|
|
|
|
dir := filepath.Dir(path)
|
|
filename := filepath.Base(path)
|
|
if strings.HasSuffix(filename, ".go") &&
|
|
!strings.HasSuffix(filename, ".pb.go") &&
|
|
!strings.Contains(dir, filepath.Join("testing", "mocks")) &&
|
|
!strings.Contains(path, filepath.Join("main", "distro", "all", "all.go")) {
|
|
jobs <- path
|
|
}
|
|
|
|
return nil
|
|
})
|
|
close(jobs)
|
|
wg.Wait()
|
|
|
|
if walkErr != nil {
|
|
fmt.Println(walkErr)
|
|
os.Exit(1)
|
|
}
|
|
|
|
if hasErrors.Load() {
|
|
os.Exit(1)
|
|
}
|
|
|
|
if isFormat {
|
|
if formatRequired.Load() {
|
|
fmt.Println("Do NOT forget to commit file changes.")
|
|
}
|
|
}
|
|
|
|
if isCheck {
|
|
if formatRequired.Load() {
|
|
fmt.Println("Format problem(s) found.")
|
|
fmt.Println("Please run 'go run ./infra/vformat/main.go' to format the Go source files.")
|
|
os.Exit(1)
|
|
} else {
|
|
fmt.Println("All Go source file format check has been passed.")
|
|
}
|
|
}
|
|
}
|
|
|
|
// diff algorithm copied from mvdan.cc/gofumpt/internal/govendor/diff
|
|
type pair struct{ x, y int }
|
|
|
|
func diff(oldName string, old []byte, newName string, new []byte) []byte {
|
|
if bytes.Equal(old, new) {
|
|
return nil
|
|
}
|
|
x := diffLines(old)
|
|
y := diffLines(new)
|
|
|
|
var out bytes.Buffer
|
|
fmt.Fprintf(&out, "diff %s %s\n", oldName, newName)
|
|
fmt.Fprintf(&out, "--- %s\n", oldName)
|
|
fmt.Fprintf(&out, "+++ %s\n", newName)
|
|
|
|
var (
|
|
done pair
|
|
chunk pair
|
|
count pair
|
|
ctext []string
|
|
)
|
|
for _, m := range diffTgs(x, y) {
|
|
if m.x < done.x {
|
|
continue
|
|
}
|
|
start := m
|
|
for start.x > done.x && start.y > done.y && x[start.x-1] == y[start.y-1] {
|
|
start.x--
|
|
start.y--
|
|
}
|
|
end := m
|
|
for end.x < len(x) && end.y < len(y) && x[end.x] == y[end.y] {
|
|
end.x++
|
|
end.y++
|
|
}
|
|
|
|
for _, s := range x[done.x:start.x] {
|
|
ctext = append(ctext, "-"+s)
|
|
count.x++
|
|
}
|
|
for _, s := range y[done.y:start.y] {
|
|
ctext = append(ctext, "+"+s)
|
|
count.y++
|
|
}
|
|
|
|
const C = 3
|
|
if (end.x < len(x) || end.y < len(y)) &&
|
|
(end.x-start.x < C || (len(ctext) > 0 && end.x-start.x < 2*C)) {
|
|
for _, s := range x[start.x:end.x] {
|
|
ctext = append(ctext, " "+s)
|
|
count.x++
|
|
count.y++
|
|
}
|
|
done = end
|
|
continue
|
|
}
|
|
|
|
if len(ctext) > 0 {
|
|
n := end.x - start.x
|
|
if n > C {
|
|
n = C
|
|
}
|
|
for _, s := range x[start.x : start.x+n] {
|
|
ctext = append(ctext, " "+s)
|
|
count.x++
|
|
count.y++
|
|
}
|
|
done = pair{start.x + n, start.y + n}
|
|
|
|
if count.x > 0 {
|
|
chunk.x++
|
|
}
|
|
if count.y > 0 {
|
|
chunk.y++
|
|
}
|
|
fmt.Fprintf(&out, "@@ -%d,%d +%d,%d @@\n", chunk.x, count.x, chunk.y, count.y)
|
|
for _, s := range ctext {
|
|
out.WriteString(s)
|
|
}
|
|
count.x = 0
|
|
count.y = 0
|
|
ctext = ctext[:0]
|
|
}
|
|
|
|
if end.x >= len(x) && end.y >= len(y) {
|
|
break
|
|
}
|
|
|
|
chunk = pair{end.x - C, end.y - C}
|
|
for _, s := range x[chunk.x:end.x] {
|
|
ctext = append(ctext, " "+s)
|
|
count.x++
|
|
count.y++
|
|
}
|
|
done = end
|
|
}
|
|
|
|
return out.Bytes()
|
|
}
|
|
|
|
func diffLines(x []byte) []string {
|
|
l := strings.SplitAfter(string(x), "\n")
|
|
if l[len(l)-1] == "" {
|
|
l = l[:len(l)-1]
|
|
} else {
|
|
l[len(l)-1] += "\n\\ No newline at end of file\n"
|
|
}
|
|
return l
|
|
}
|
|
|
|
func diffTgs(x, y []string) []pair {
|
|
m := make(map[string]int)
|
|
for _, s := range x {
|
|
if c := m[s]; c > -2 {
|
|
m[s] = c - 1
|
|
}
|
|
}
|
|
for _, s := range y {
|
|
if c := m[s]; c > -8 {
|
|
m[s] = c - 4
|
|
}
|
|
}
|
|
|
|
var xi, yi, inv []int
|
|
for i, s := range y {
|
|
if m[s] == -5 {
|
|
m[s] = len(yi)
|
|
yi = append(yi, i)
|
|
}
|
|
}
|
|
for i, s := range x {
|
|
if j, ok := m[s]; ok && j >= 0 {
|
|
xi = append(xi, i)
|
|
inv = append(inv, j)
|
|
}
|
|
}
|
|
|
|
J := inv
|
|
n := len(xi)
|
|
T := make([]int, n)
|
|
L := make([]int, n)
|
|
for i := range T {
|
|
T[i] = n + 1
|
|
}
|
|
for i := 0; i < n; i++ {
|
|
k := sort.Search(n, func(k int) bool {
|
|
return T[k] >= J[i]
|
|
})
|
|
T[k] = J[i]
|
|
L[i] = k + 1
|
|
}
|
|
k := 0
|
|
for _, v := range L {
|
|
if k < v {
|
|
k = v
|
|
}
|
|
}
|
|
seq := make([]pair, 2+k)
|
|
seq[1+k] = pair{len(x), len(y)}
|
|
lastj := n
|
|
for i := n - 1; i >= 0; i-- {
|
|
if L[i] == k && J[i] < lastj {
|
|
seq[k] = pair{xi[i], yi[J[i]]}
|
|
k--
|
|
}
|
|
}
|
|
seq[0] = pair{0, 0}
|
|
return seq
|
|
}
|