Initial commit

Signed-off-by: jolheiser <john.olheiser@gmail.com>
pull/1/head
jolheiser 2020-05-06 17:05:00 -05:00
commit 318f5c0d77
No known key found for this signature in database
GPG Key ID: 83E486E71AFEB820
8 changed files with 384 additions and 0 deletions

5
.gitignore vendored 100644
View File

@ -0,0 +1,5 @@
# GoLand
.idea/
# Binaries
/imp*

21
Makefile 100644
View File

@ -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

20
README.md 100644
View File

@ -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

11
go.mod 100644
View File

@ -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
)

48
go.sum 100644
View File

@ -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=

194
imp.go 100644
View File

@ -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)
}

36
main.go 100644
View File

@ -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)
}
}

49
struct.go 100644
View File

@ -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
)