This repository has been archived on 2023-11-08. You can view files and clone it, but cannot push or open issues/pull-requests.
imp/imp.go

204 lines
4.3 KiB
Go

package main
import (
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sort"
"strings"
"gitea.com/jolheiser/globber"
"github.com/gobuffalo/here"
"github.com/urfave/cli/v2"
"go.jolheiser.com/beaver"
"go.jolheiser.com/beaver/color"
"go.jolheiser.com/regexp"
)
var (
module string
importRe = regexp.MustCompile(`(?ms)import \(([^)]+)\)`)
otherRe = regexp.MustCompile(`(?:var|const|func)\s`)
)
func runImp(ctx *cli.Context) error {
if ctx.Bool("verbose") {
beaver.Console.Level = beaver.DEBUG
}
info, err := here.Current()
if err != nil {
return err
}
module = info.Module.Path
beaver.Debugf("Current module: %s", module)
globs, err := globber.ParseFile(ctx.String("imp-ignore"))
if err != nil {
if !os.IsNotExist(err) {
return err
}
globs = globber.New()
}
var failed bool
diffColor := color.New(color.FgGreen, color.BgHiBlack)
if err := filepath.Walk(".", func(walkPath string, walkInfo os.FileInfo, walkErr error) error {
if walkErr != nil {
return walkErr
}
walkPath = filepath.ToSlash(walkPath)
if s := checkSkip(walkPath, walkInfo, globs); s != CHECK {
if s == SKIPDIR {
return filepath.SkipDir
}
return nil
}
beaver.Debugf("Checking file %s", walkPath)
data, err := ioutil.ReadFile(walkPath)
if err != nil {
return err
}
importStart := importRe.FindIndex(data)
if importStart == nil {
return nil
}
otherStart := otherRe.FindIndex(data)
if otherStart != nil && otherStart[0] < importStart[0] {
return nil
}
groups := importRe.Groups(string(data))
if groups.Index(0) == "" {
return nil
}
imports := strings.Split(groups.Index(1), "\n")
for idx, i := range imports {
imports[idx] = strings.TrimSpace(i)
}
formatted := formatImportStmt(splitImports(imports))
if ctx.Bool("write") {
replaced := strings.Replace(string(data), groups.Index(0), formatted, 1)
return ioutil.WriteFile(walkPath, []byte(replaced), walkInfo.Mode())
}
if !strings.EqualFold(groups.Index(0), formatted) {
failed = true
beaver.Infof("File: %s", diffColor.Format(walkPath))
beaver.Infof("Expected:\n%s", diffColor.Format(formatted))
beaver.Infof("Got:\n%s\n", diffColor.Format(groups.Index(0)))
}
return nil
}); err != nil {
return err
}
if failed {
return errors.New("imports are formatted incorrectly; this can be fixed with the `--write` flag")
}
return nil
}
func checkSkip(walkPath string, walkInfo os.FileInfo, globs *globber.GlobSet) walkStatus {
// Skip current directory
if strings.EqualFold(walkPath, ".") {
return SKIP
}
// Skip hidden paths (starting with ".")
if strings.HasPrefix(walkPath, ".") {
beaver.Debugf("Skipping hidden path %s", walkPath)
if walkInfo.IsDir() {
return SKIPDIR
}
return SKIP
}
// Skip directories
if walkInfo.IsDir() {
return SKIP
}
// Skip non-Go files
if !strings.HasSuffix(walkInfo.Name(), ".go") {
beaver.Debugf("Skipping non-Go file %s", walkPath)
return SKIP
}
// Skip included (ignored) globs
i, e := globs.Explain(walkPath)
if len(i) > 0 && len(e) == 0 {
beaver.Debugf("Skipping file %s because of .impignore rule %s", walkPath, i[0].Pattern)
return SKIP
}
return CHECK
}
func splitImports(imports []string) []importList {
// 0 -> stdlib
// 1 -> this module
// 2 -> others
split := []importList{{}, {}, {}}
var comment string
for _, imp := range imports {
if imp == "" {
continue
}
i := imp
if strings.HasPrefix(i, `//`) {
var nl string
if comment != "" {
nl = "\n"
}
comment = nl + i
continue
}
var name string
if !strings.HasPrefix(i, `"`) {
parts := strings.SplitN(i, " ", 2)
name = parts[0]
i = parts[1]
}
switch {
case strings.HasPrefix(i, `"`+module):
split[1] = append(split[1], importItem{comment, name, i})
case strings.Contains(i, "."):
split[2] = append(split[2], importItem{comment, name, i})
default:
split[0] = append(split[0], importItem{comment, name, i})
}
comment = ""
}
for _, s := range split {
sort.Sort(s)
}
return split
}
func formatImportStmt(imports []importList) string {
var decl string
for _, imp := range imports {
var pre string
if decl != "" {
pre = "\n\n\t"
}
if len(imp) > 0 {
decl += pre + strings.Join(imp.StringSlice(), "\n\t")
}
}
return fmt.Sprintf(`import (
%s
)`, decl)
}