commit
318f5c0d77
|
@ -0,0 +1,5 @@
|
|||
# GoLand
|
||||
.idea/
|
||||
|
||||
# Binaries
|
||||
/imp*
|
|
@ -0,0 +1,21 @@
|
|||
GO ?= go
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
$(GO) build
|
||||
|
||||
.PHONY: fmt
|
||||
fmt:
|
||||
$(GO) fmt ./...
|
||||
|
||||
.PHONY: test
|
||||
test:
|
||||
$(GO) test -race ./...
|
||||
|
||||
.PHONY: vet
|
||||
vet:
|
||||
$(GO) vet ./...
|
||||
|
||||
.PHONY: imp
|
||||
imp: build
|
||||
./imp -w
|
|
@ -0,0 +1,20 @@
|
|||
# imp
|
||||
|
||||
imp is an opinionated import formatter
|
||||
|
||||
The order it follows is:
|
||||
```text
|
||||
import (
|
||||
<stdlib>
|
||||
|
||||
<this module's packages>
|
||||
|
||||
<other module's packages>
|
||||
)
|
||||
```
|
||||
|
||||
imp includes three flags:
|
||||
|
||||
* `--write` will write out the formatting rather than printing
|
||||
* `--exclude` can be used multiple times to set directories to exclude
|
||||
* `--verbose` will print out extended information
|
|
@ -0,0 +1,11 @@
|
|||
module go.jolheiser.com/imp
|
||||
|
||||
go 1.14
|
||||
|
||||
require (
|
||||
github.com/gobuffalo/here v0.6.2
|
||||
github.com/urfave/cli/v2 v2.2.0
|
||||
go.jolheiser.com/beaver v1.0.2
|
||||
go.jolheiser.com/regexp v0.1.1
|
||||
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8
|
||||
)
|
|
@ -0,0 +1,48 @@
|
|||
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d h1:U+s90UTSYgptZMwQh2aRr3LuazLJIa+Pg3Kc1ylSYVY=
|
||||
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
|
||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/gobuffalo/here v0.6.2 h1:ZtCqC7F9ou3moLbYfHM1Tj+gwHGgWhjyRjVjsir9BE0=
|
||||
github.com/gobuffalo/here v0.6.2/go.mod h1:D75Sq0p2BVHdgQu3vCRsXbg85rx943V19urJpqAVWjI=
|
||||
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
|
||||
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0 h1:PdmoCO6wvbs+7yrJyMORt4/BmY5IYyJwS/kOiWx8mHo=
|
||||
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
|
||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
|
||||
github.com/urfave/cli v1.22.4 h1:u7tSpNPPswAFymm8IehJhy4uJMlUuU/GmqSkvJ1InXA=
|
||||
github.com/urfave/cli/v2 v2.2.0 h1:JTTnM6wKzdA0Jqodd966MVj4vWbbquZykeX1sKbe2C4=
|
||||
github.com/urfave/cli/v2 v2.2.0/go.mod h1:SE9GqnLQmjVa0iPEY0f1w3ygNIYcIJ0OKPMoW2caLfQ=
|
||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||
go.jolheiser.com/beaver v1.0.2 h1:KA2D6iO8MQhZi1nZYi/Chak/f1Cxfrs6b1XO623+Khk=
|
||||
go.jolheiser.com/beaver v1.0.2/go.mod h1:7X4F5+XOGSC3LejTShoBdqtRCnPWcnRgmYGmG3EKW8g=
|
||||
go.jolheiser.com/regexp v0.1.1 h1:nMCzilEL/oIcZJwnRT4bb+FNxaeAFBdqSjq7rgoNSGg=
|
||||
go.jolheiser.com/regexp v0.1.1/go.mod h1:58uCpYxGy/DbqVRuo6oU93kh3B3GeyK5eJznQvy2N6c=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
|
||||
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
|
||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a h1:aYOabOQFp6Vj6W1F80affTUvO9UxmJRx8K0gsfABByQ=
|
||||
golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
|
||||
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8 h1:BMFHd4OFnFtWX46Xj4DN6vvT1btiBxyq+s0orYBqcQY=
|
||||
golang.org/x/tools v0.0.0-20200505023115-26f46d2f7ef8/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
|
||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||
gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
|
@ -0,0 +1,194 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"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)
|
||||
|
||||
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, ctx.StringSlice("exclude")); 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, excludePaths []string) 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 excluded directories
|
||||
for _, e := range excludePaths {
|
||||
if strings.HasPrefix(walkPath, e) {
|
||||
beaver.Debugf("Skipping file %s because of exclude path %s", walkPath, e)
|
||||
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.Split(i, " ")
|
||||
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)
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"os"
|
||||
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.jolheiser.com/beaver"
|
||||
)
|
||||
|
||||
func main() {
|
||||
app := cli.NewApp()
|
||||
app.Name = "Imp"
|
||||
app.Usage = "Re-order imports"
|
||||
app.Flags = []cli.Flag{
|
||||
&cli.BoolFlag{
|
||||
Name: "write",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "Write the re-ordered imports instead of just printing them",
|
||||
},
|
||||
&cli.StringSliceFlag{
|
||||
Name: "exclude",
|
||||
Aliases: []string{"e"},
|
||||
Usage: "A list of directories to exclude",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "Print more information",
|
||||
},
|
||||
}
|
||||
app.Action = runImp
|
||||
|
||||
if err := app.Run(os.Args); err != nil {
|
||||
beaver.Fatal(err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
package main
|
||||
|
||||
type importItem struct {
|
||||
Comment string
|
||||
Name string
|
||||
Path string
|
||||
}
|
||||
|
||||
func (ii importItem) String() string {
|
||||
var comment string
|
||||
if ii.Comment != "" {
|
||||
comment = ii.Comment + "\n\t"
|
||||
}
|
||||
var name string
|
||||
if ii.Name != "" {
|
||||
name = ii.Name + " "
|
||||
}
|
||||
return comment + name + ii.Path
|
||||
}
|
||||
|
||||
type importList []importItem
|
||||
|
||||
func (il importList) Len() int {
|
||||
return len(il)
|
||||
}
|
||||
|
||||
func (il importList) Less(i, j int) bool {
|
||||
return il[i].Path < il[j].Path
|
||||
}
|
||||
|
||||
func (il importList) Swap(i, j int) {
|
||||
il[i], il[j] = il[j], il[i]
|
||||
}
|
||||
|
||||
func (il importList) StringSlice() []string {
|
||||
s := make([]string, len(il))
|
||||
for idx, ii := range il {
|
||||
s[idx] = ii.String()
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
type walkStatus int
|
||||
|
||||
const (
|
||||
SKIP walkStatus = iota
|
||||
SKIPDIR
|
||||
CHECK
|
||||
)
|
Reference in New Issue