Skip to content

Commit

Permalink
Merge pull request #6 from coreydaley/add_files_flag
Browse files Browse the repository at this point in the history
Adding -p flag to specify individual paths to organize
  • Loading branch information
coreydaley authored Mar 11, 2023
2 parents 3697da7 + 6bc7d2e commit 0583713
Show file tree
Hide file tree
Showing 12 changed files with 198 additions and 88 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ build: ## Build the executable. Example: make build
.PHONY: build

clean: ## Clean up the workspace. Example: make clean
rm -rf .goio *.pprof profile*
rm -rf goio *.pprof profile*
.PHONY: clean

help: ## Print this help. Example: make help
Expand Down
67 changes: 50 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,44 @@
A customizable imports organizer for the Go programming language

* [Summary](#summary)
* [Installation](#installation)
* [Command Line Tool](#command-line-tool)
* [CI/CD Integration](#ci-cd-integration)
* [Configuration](#configuration)
* [Profiling](#profiling)

# <a name='summary'></a>Summary
`goio` is a fully customizable Go imports organizer. The configuration
is project based and is stored in a `goio.yaml` file in the root of your
module's project folder alongside the `go.mod` file. For consistency
the `goio.yaml` file should be committed to your projects vcs.

# <a name='installation'></a>Installation

## Command Line Tool

# <a name='command-line-tool'></a>Command Line Tool

## Installation
```
$ go install github.com/go-imports-organizer/goio@latest
```

## Go project configuration
## Usage
```
Usage of goio:
-p value
path to a file or directory to organize, use multiple times for multiple files
-l list files that need to be organized (no changes made)
-v print version and exit
### Example scripts/tools.go file
```
# <a name='ci-cd-configuration'></a>CI/CD Configuration

## Example scripts/tools.go file
This file will ensure that the `github.com/go-imports-organizer/goio` repo is vendored into your project.
```
//go:build tools
// +build tools
package hack
package scripts
// Add tools that scripts depend on here, to ensure they are vendored.
import (
Expand All @@ -37,7 +49,7 @@ import (
```

### Example scripts/verify-imports.sh script
## Example scripts/verify-imports.sh script
This file will check if there are any go files that need to be formatted. If there are, it will print a list of them, and exit with status one (1), otherwise it will exit with status zero (0). Make sure that you make the file executable with `chmod +x scripts/verify-imports.sh`.
```
#!/bin/bash
Expand All @@ -51,7 +63,7 @@ if [[ -n "${bad_files}" ]]; then
fi
```

### Example Makefile sections
## Example Makefile sections
```
imports: ## Organize imports in go files using goio. Example: make imports
go run ./vendor/github.com/go-imports-organizer/goio
Expand All @@ -62,46 +74,67 @@ verify-imports: ## Run import verifications. Example: make verify-imports
.PHONY: verify-imports
```

# <a name='Configuration'></a>Configuration
# <a name='configuration-file'></a>Configuration File
The `goio.yaml` configuration file is a well formatted yaml file.

### Excludes
## Excludes
An array of Exclude definitions.

Each Exclude definition is a rule that tells `goio` which files and folders to ignore while looking for Go files that it should organize the imports of. The default configuration file ignores the `.git` directory and the `vendor` directory. The more files and folders that you can ignore at this stage the faster `goio` will run.

#### RegExp
### RegExp
A string, valid values are any valid Go regular expression.

A well formatted Regular Expression that is used to match against. Be as specific as possible.

#### MatchType
### MatchType
A string, valid values are `[name, path]`.

Lets `goio` know whether to match agains the files `name` or the files `path` relative to the modules root directory _(where the go.mod file is located)_.

### Groups
## Groups
An array of Group definitions

Each group definition is a rule that tells `goio` how you would like the `imports` in your Go files organized. Each definition represents a block of import statements, describing how to identify the items that it should contain. Group blocks are displayed in **the order that they appear in the array**.

#### Description
### Description
A string, valid values are any valid string value

A friendly name to identify the definition by instead of trying to decipher the regular expression each time to remember what it does.

#### RegExp
### RegExp
An array of strings, valid values are any valid Go regular expression.

A well formatted Regular Expression that is used to match against. Be as specific as possible.

**Note:**
**NOTE:**

There is one keyword that is available for the RegExp value that is a special keyword, it is `%{module}%`. This keyword automatically creates a regular expression that matches the current module name as defined by the go.mod file. To ensure that it captures the correct imports you should always set the `MatchOrder` to `0` for this definition.

#### MatchOrder
### MatchOrder
An integer, valid values are -n...n

Tells `goio` which order the definitions should be matched against in. Lower numbers are first, higher numbers are last.

It is important to ensure the correct `matchorder` is used, expecially if any of your `regexp` have any kind of overlap, such as having a module name of `github.com/example/mymodule` and a group definition for `github.com/example`. You would want to make sure that your `module` definition was matched first or those imports would get rolled into the `github.com/example` one because it is less specific.

**NOTE**
The recommended Match Order to avoid the above scenario is:
- module *(should always be first)*
- custom groups *(ordered most specific to least specific)*
- standard *(should be second to last)*
- other *(should be last)*

# <a name='profiling'></a>Profiling
Profiling via the `pprof` tools is already configured within the application and can be enabled using the following methods.
## CPU Profiling
You can enable CPU profiling by setting the `CPUPROFILE` environment variable to the name of the file that the profiling information should be written to.
```
$ CPUPROFILE=cpu.pprof goio
```

## Memory Profiling
You can enable MEMORY profiling by setting the `MEMPROFILE` environment variable to the name of the file that the profiling information should be written to.
```
$ MEMPROFILE=mem.pprof goio
```
6 changes: 3 additions & 3 deletions examples/openshift_kubernetes.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@ excludes:
regexp: ^vendor$
groups:
- description: standard
matchorder: 0
matchorder: 3
regexp:
- ^[a-zA-Z0-9\/]+$
- description: kubernetes
matchorder: 1
regexp:
- ^k8s\.io
- description: openshift
matchorder: 3
matchorder: 2
regexp:
- ^github\.com\/openshift
- description: other
matchorder: 4
regexp:
- '[a-zA-Z0-9]+\.[a-zA-Z0-9]+/'
- description: module
matchorder: 2
matchorder: 0
regexp:
- "%{module}%"
105 changes: 71 additions & 34 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,14 @@ package main
import (
"flag"
"fmt"
"log"
"os"
"path/filepath"
"runtime"
"runtime/pprof"
"strings"
"sync"

v1alpha1 "github.com/go-imports-organizer/goio/pkg/api/v1alpha1"
"github.com/go-imports-organizer/goio/pkg/config"
"github.com/go-imports-organizer/goio/pkg/excludes"
"github.com/go-imports-organizer/goio/pkg/groups"
Expand All @@ -35,46 +35,52 @@ import (
)

var (
wg sync.WaitGroup
files = make(chan string)
wg sync.WaitGroup
files = make(chan string)
pathList v1alpha1.PathListFlags
)

func main() {
// If the -l flag is set, only return a list of what files would have been formatted, but don't make any changes
listOnly := flag.Bool("l", false, "list files that need to be organized (no changes made)")
listOnly := flag.Bool("l", false, "only list files that need to be organized (no changes made)")
flag.Var(&pathList, "p", "path to a file to organize, use multiple times for multiple files")
versionOnly := flag.Bool("v", false, "print version and exit")
flag.Parse()

// set CPUPROFILE=<filename> to create a <filename>.pprof cpu profile file
if len(os.Getenv("CPUPROFILE")) != 0 {
fmt.Fprintf(os.Stdout, "Logging CPU profiling information to %s\n", os.Getenv(("CPUPROFILE")))
f, err := os.Create(os.Getenv("CPUPROFILE"))
if err != nil {
log.Fatal(err)
fmt.Fprintf(os.Stderr, "%s", err.Error())
os.Exit(1)
}
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}

if *versionOnly {
fmt.Fprintf(os.Stdout, "%s\n", version.Get())
os.Exit(0)
}

currentDir, err := os.Getwd()
if err != nil {
log.Fatalf("%s", err.Error())
fmt.Fprintf(os.Stderr, "unable to get current working directory: %s\n", err.Error())
os.Exit(1)
}

// Find the Go module name and path
goModuleName, goModulePath, err := module.FindGoModuleNameAndPath(currentDir)
if err != nil {
log.Fatalf("error occurred finding module path: %s", err.Error())
fmt.Fprintf(os.Stderr, "error occurred finding module path: %s\n", err.Error())
os.Exit(1)
}

// Load the configuration from the goio.yaml file
conf, err := config.Load("goio.yaml")
if err != nil {
log.Fatalf("%s", err.Error())
fmt.Fprintf(os.Stderr, "error occurred loading configuration file: %s\n", err.Error())
os.Exit(1)
}

// Build the Regular Expressions for excluding files/folders
Expand All @@ -93,39 +99,67 @@ func main() {
// Change our working directory to the goModulePath
err = os.Chdir(goModulePath)
if err != nil {
panic(err)
fmt.Fprintf(os.Stderr, "unable to change directory to %q: %s\n", goModulePath, err.Error())
os.Exit(1)
}

// Pre-optization so that we can skip the Name or Path matches if they are empty
excludeByNameRegExpLenOk := len(excludeByNameRegExp.String()) != 0
excludeByPathRegExpLenOk := len(excludeByPathRegExp.String()) != 0

// Walk the filepath looking for Go files
if err = filepath.Walk(".", func(path string, f os.FileInfo, err error) error {
// If no paths are supplied via the -p flag use the current directory
if len(pathList) == 0 {
pathList = append(pathList, goModulePath)
}

for _, path := range pathList {
f, err := os.Stat(path)
if err != nil {
return err
fmt.Fprintf(os.Stderr, "unable to stat %q: %s\n", path, err.Error())
continue
}
name := f.Name()
isDir := f.IsDir()
isGoFile := strings.HasSuffix(name, ".go")
relativePath := strings.Replace(path, basePath, "", 1)
// If the object is not a directory and not a Go file, skip it
if isDir || isGoFile {
// If the objects name or path matches an exclude Regular Expression, skip it
if (excludeByNameRegExpLenOk && excludeByNameRegExp.MatchString(name)) || (excludeByPathRegExpLenOk && excludeByPathRegExp.MatchString(relativePath)) {
if isDir {
return filepath.SkipDir

// If the path is a Go file
if strings.HasSuffix(path, ".go") {
// If the files name or path matches an exclude Regular Expression, skip it
if (excludeByNameRegExpLenOk && excludeByNameRegExp.MatchString(f.Name())) || (excludeByPathRegExpLenOk && excludeByPathRegExp.MatchString(path)) {
continue
}
// If the file is not excluded by name or path, queue it for organizing
files <- path

} else if f.IsDir() {
// If the path is a directory
if err = filepath.Walk(path, func(path string, f os.FileInfo, err error) error {
if err != nil {
return err
}
name := f.Name()
isDir := f.IsDir()
isGoFile := strings.HasSuffix(name, ".go")
relativePath := strings.Replace(path, basePath, "", 1)
// If the object is not a directory and not a Go file, skip it
if isDir || isGoFile {
// If the objects name or path matches an exclude Regular Expression, skip it
if (excludeByNameRegExpLenOk && excludeByNameRegExp.MatchString(name)) || (excludeByPathRegExpLenOk && excludeByPathRegExp.MatchString(relativePath)) {
// If the object is a Directory, skip the entire thing
if isDir {
return filepath.SkipDir
}
return nil
}

// If the object is a Go file and is not excluded, queue it for organizing
if isGoFile {
files <- relativePath
}
}
return nil
}
// If the object is a Go file and is not excluded, queue it for formatting
if isGoFile {
files <- relativePath
}); err != nil {
fmt.Fprintf(os.Stderr, "unable to complete walking file tree: %s\n", err.Error())
}
}
return nil
}); err != nil {
log.Fatalf("unable to complete walking file tree: %s", err.Error())

}

// Close the files channel since we are done queuing up files to format
Expand All @@ -136,14 +170,17 @@ func main() {

// set MEMPROFILE=<filename> to create a <filename>.pprof memory profile file
if len(os.Getenv("MEMPROFILE")) != 0 {
fmt.Fprintf(os.Stdout, "Logging MEMORY profiling information to %s\n", os.Getenv(("MEMPROFILE")))
f, err := os.Create(os.Getenv("MEMPROFILE"))
if err != nil {
log.Fatal("could not create memory profile: ", err)
fmt.Fprintf(os.Stderr, "%s", err.Error())
os.Exit(1)
}
defer f.Close() // error handling omitted for example
runtime.GC() // get up-to-date statistics
defer f.Close()
runtime.GC()
if err := pprof.WriteHeapProfile(f); err != nil {
log.Fatal("could not write memory profile: ", err)
fmt.Fprintf(os.Stderr, "could not write memory profile: %s", err.Error())
os.Exit(1)
}
}
}
Loading

0 comments on commit 0583713

Please sign in to comment.