Skip to content

Commit

Permalink
Task for "mvdan.cc/gofumpt" Go module command (#57)
Browse files Browse the repository at this point in the history
The "mvdan.cc/gofumpt" [1] Go module provides the `gofumpt` command,
a tool that enforces a stricter format than
"https://pkg.go.dev/cmd/gofmt" and provides additional rules [2],
while being backwards compatible.
It is a modified fork of "https://pkg.go.dev/cmd/gofmt" so it can be
used as a drop-in replacement.

To configure and run the `gofumpt` command, a new `task.GoModule` [3]
has been implemented in the new "gofumpt" [4] package that can be run
using the "gobin" command runner [5] or any other command runner [6]
that handles tasks of kind `KindGoModule` [7].

The task is customizable through the following functions:

- `WithEnv(map[string]string) gofumpt.Option` - sets the task specific
  environment.
- `WithExtraArgs(...string) gofumpt.Option` - sets additional arguments
  to pass to the command.
- `WithExtraRules(bool) gofumpt.Option` - indicates whether gofumpt's
  extra rules should be enabled. See the [repository documentation for a
  listing of available rules][2].
- `WithListNonCompliantFiles(bool) gofumpt.Option` - indicates whether
  files, whose formatting are not conform to the style guide, are
  listed.
- `WithModulePath(string) gofumpt.Option` - sets the module import path.
- `WithModuleVersion(*semver.Version) gofumpt.Option` - sets the module
  version.
- `WithPaths(...string) gofumpt.Option` - sets the paths to search for
  Go source files. By default all directories are scanned recursively
  starting from the current working directory.
- `WithReportAllErrors(bool) gofumpt.Option` - indicates whether all
  errors should be printed instead of only the first 10 on different
  lines.
- `WithSimplify(bool) gofumpt.Option` - indicates whether code should
  be simplified.

The "elder" reference implementation provides the new `Gofumpt`
method [8].

[1]: https://pkg.go.dev/mvdan.cc/gofumpt
[2]: https://github.com/mvdan/gofumpt#added-rules
[3]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task#GoModule
[4]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task/gofumpt
[5]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task/gobin#Runner
[6]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task#Runner
[7]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task#KindGoModule
[8]: https://pkg.go.dev/github.com/svengreb/wand/pkg/elder#Elder.Gofumpt

Closes GH-56
  • Loading branch information
svengreb authored Dec 11, 2020
1 parent 096fc50 commit 3273e91
Show file tree
Hide file tree
Showing 12 changed files with 300 additions and 9 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ The [`task`][go-pkg-task] package defines the API for tasks. [`Task`][go-pkg-if-

The package also already provides tasks for basic [Go toolchain][go-pkg-cmd/go] commands and popular modules from the Go ecosystem:

- **`gofumpt`** — the [`gofumpt`][go-pkg-task/gofumpt] package provides a task for the [`mvdan.cc/gofumpt`][go-pkg-mvdan.cc/gofumpt] Go module command. `gofumpt` enforces a stricter format than [`gofmt`][go-pkg-cmd/gofmt] and provides additional rules, while being backwards compatible. It is a modified fork of `gofmt` so it can be used as a drop-in replacement.
- **`goimports`** — the [`goimports`][go-pkg-task/goimports] package provides a task for the [`golang.org/x/tools/cmd/goimports`][go-pkg-golang.org/x/tools/cmd/goimports] Go module command. `goimports` allows to update Go import lines, add missing ones and remove unreferenced ones. It also formats code in the same style as [`gofmt`][go-pkg-cmd/gofmt] so it can be used as a replacement. The source code of `goimports` is [available in the GitHub repository][gh-golang/tools-tree-cmd/goimports].
- **Go** — The [`golang`][go-pkg-task/golang] package provides tasks for [Go toolchain][go-pkg-cmd/go] commands.
- **`build`** — to run the [`build` command of the Go toolchain][go-pkg-cmd/go#build] the task of the [`build`][go-pkg-task/golang/build] package can be used.
Expand Down Expand Up @@ -347,6 +348,7 @@ The guide also includes information about [minimal, complete, and verifiable exa
[go-pkg-if-task#runnerexec]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task#RunnerExec
[go-pkg-if-task#task]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task#Task
[go-pkg-if-wand#wand]: https://pkg.go.dev/github.com/svengreb/wand#Wand
[go-pkg-mvdan.cc/gofumpt]: https://pkg.go.dev/mvdan.cc/gofumpt
[go-pkg-pkg]: https://pkg.go.dev/github.com/svengreb/wand/pkg
[go-pkg-pkg/go/build#constraints]: https://pkg.go.dev/pkg/go/build/#hdr-Build_Constraints
[go-pkg-project]: https://pkg.go.dev/github.com/svengreb/wand/pkg/project
Expand All @@ -357,6 +359,7 @@ The guide also includes information about [minimal, complete, and verifiable exa
[go-pkg-stc-task/golang#runner]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task/golang#Runner
[go-pkg-task]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task
[go-pkg-task/fs/clean]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task/fs/clean
[go-pkg-task/gofumpt]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task/gofumpt
[go-pkg-task/goimports]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task/goimports
[go-pkg-task/golang]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task/golang
[go-pkg-task/golang/build]: https://pkg.go.dev/github.com/svengreb/wand/pkg/task/golang/build
Expand Down
33 changes: 26 additions & 7 deletions pkg/elder/elder.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"github.com/svengreb/wand/pkg/task"
taskFSClean "github.com/svengreb/wand/pkg/task/fs/clean"
taskGobin "github.com/svengreb/wand/pkg/task/gobin"
taskGofumpt "github.com/svengreb/wand/pkg/task/gofumpt"
taskGoimports "github.com/svengreb/wand/pkg/task/goimports"
taskGo "github.com/svengreb/wand/pkg/task/golang"
taskGoBuild "github.com/svengreb/wand/pkg/task/golang/build"
Expand Down Expand Up @@ -70,7 +71,7 @@ func (e *Elder) Clean(appName string, opts ...taskFSClean.Option) ([]string, err
}
t, tErr := taskFSClean.New(e.GetProjectMetadata(), ac, opts...)
if tErr != nil {
return []string{}, fmt.Errorf("create %q task: %w", "fs/clean", tErr)
return []string{}, fmt.Errorf("create %q task: %w", taskFSClean.TaskName, tErr)
}

return t.Clean()
Expand Down Expand Up @@ -132,8 +133,26 @@ func (e *Elder) GoBuild(appName string, opts ...taskGoBuild.Option) error {
return fmt.Errorf("get %q application configuration: %w", appName, acErr)
}

t := taskGoBuild.New(e, ac, opts...)
return e.goRunner.Run(t)
return e.goRunner.Run(taskGoBuild.New(e, ac, opts...))
}

// Gofumpt is a task for the "mvdan.cc/gofumpt" Go module command.
// "gofumpt" enforce a stricter format than "https://pkg.go.dev/cmd/gofmt", while being backwards compatible,
// and provides additional rules.
// It is a modified fork of "https://pkg.go.dev/cmd/gofmt" so it can be used as a drop-in replacement.
//
// See the "github.com/svengreb/wand/pkg/task/gofumpt" package for all available options.
// See https://github.com/mvdan/gofumpt#added-rules for more details about available rules.
//
// See https://pkg.go.dev/mvdan.cc/gofumpt for more details about "gofumpt".
// The source code of "gofumpt" is available at https://github.com/mvdan/gofumpt.
func (e *Elder) Gofumpt(appName string, opts ...taskGofumpt.Option) error {
ac, acErr := e.GetAppConfig(appName)
if acErr != nil {
return fmt.Errorf("get %q application configuration: %w", appName, acErr)
}

return e.gobinRunner.Run(taskGofumpt.New(e, ac, opts...))
}

// Goimports is a task for the "golang.org/x/tools/cmd/goimports" Go module command.
Expand All @@ -152,7 +171,7 @@ func (e *Elder) Goimports(appName string, opts ...taskGoimports.Option) error {

t, tErr := taskGoimports.New(e, ac, opts...)
if tErr != nil {
return fmt.Errorf("create %q task: %w", "goimports", tErr)
return fmt.Errorf("create %q task: %w", taskGoimports.TaskName, tErr)
}

return e.gobinRunner.Run(t)
Expand All @@ -177,7 +196,7 @@ func (e *Elder) GolangCILint(appName string, opts ...taskGolangCILint.Option) er

t, tErr := taskGolangCILint.New(e, ac, opts...)
if tErr != nil {
return fmt.Errorf("create %q task: %w", "golangci-lint", tErr)
return fmt.Errorf("create %q task: %w", taskGolangCILint.TaskName, tErr)
}

return e.gobinRunner.Run(t)
Expand Down Expand Up @@ -225,7 +244,7 @@ func (e *Elder) Gox(appName string, opts ...taskGox.Option) error {

t, tErr := taskGox.New(e, ac, opts...)
if tErr != nil {
return fmt.Errorf("create %q task: %w", "gox", tErr)
return fmt.Errorf("create %q task: %w", taskGox.TaskName, tErr)
}

return e.gobinRunner.Run(t)
Expand Down Expand Up @@ -270,7 +289,7 @@ func (e *Elder) Pkger(appName string, opts ...taskPkger.Option) error {

t, tErr := taskPkger.New(e, ac, opts...)
if tErr != nil {
return fmt.Errorf("create %q task: %w", "pkger", tErr)
return fmt.Errorf("create %q task: %w", taskPkger.TaskName, tErr)
}

dummyWorkaroundFilePath := filepath.Join(
Expand Down
5 changes: 5 additions & 0 deletions pkg/task/fs/clean/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,11 @@

package clean

const (
// TaskName is the name of the task.
TaskName = "fs/clean"
)

// Option is a task option.
type Option func(*Options)

Expand Down
100 changes: 100 additions & 0 deletions pkg/task/gofumpt/gofumpt.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

// Package gofumpt provides a task for the "mvdan.cc/gofumpt" Go module command.
// "gofumpt" enforce a stricter format than "https://pkg.go.dev/cmd/gofmt" and provides additional rules, while being
// backwards compatible.
// It is a modified fork of "https://pkg.go.dev/cmd/gofmt" so it can be used as a drop-in replacement.
//
// See https://pkg.go.dev/mvdan.cc/gofumpt for more details about "gofumpt".
// The source code of "gofumpt" is available at https://github.com/mvdan/gofumpt.
// See https://github.com/mvdan/gofumpt#added-rules for more details about available rules.
package gofumpt

import (
"github.com/svengreb/wand"
"github.com/svengreb/wand/pkg/app"
"github.com/svengreb/wand/pkg/project"
"github.com/svengreb/wand/pkg/task"
)

// Task is a task for the "mvdan.cc/gofumpt" Go module command.
// "gofumpt" enforce a stricter format than "https://pkg.go.dev/cmd/gofmt", while being backwards compatible,
// and provides additional rules.
// It is a modified fork of "https://pkg.go.dev/cmd/gofmt" so it can be used as a drop-in replacement.
//
// See https://pkg.go.dev/mvdan.cc/gofumpt for more details about "gofumpt".
// The source code of "gofumpt" is available at https://github.com/mvdan/gofumpt.
// See https://github.com/mvdan/gofumpt#added-rules for more details about available rules.
type Task struct {
ac app.Config
opts *Options
}

// BuildParams builds the parameters.
func (t *Task) BuildParams() []string {
var params []string

// Enable gofumpt's extra rules.
if t.opts.extraRules {
params = append(params, "-extra")
}

// List files whose formatting are non-compliant to gofumpt's styles.
if t.opts.listNonCompliantFiles {
params = append(params, "-l")
}

// Write result to source files instead of stdout.
if t.opts.persistChanges {
params = append(params, "-w")
}

// Report all errors and not just the first 10 on different lines.
if t.opts.reportAllErrors {
params = append(params, "-e")
}

if t.opts.simplify {
params = append(params, "-s")
}

// Include additionally configured arguments.
params = append(params, t.opts.extraArgs...)

// Only search in specified paths for Go source files...
if len(t.opts.paths) > 0 {
params = append(params, t.opts.paths...)
} else {
// ...or otherwise search recursively starting from the working directory of the current process.
params = append(params, ".")
}

return params
}

// Env returns the task specific environment.
func (t *Task) Env() map[string]string {
return t.opts.env
}

// ID returns the identifier of the Go module.
func (t *Task) ID() *project.GoModuleID {
return t.opts.goModule
}

// Kind returns the task kind.
func (t *Task) Kind() task.Kind {
return task.KindGoModule
}

// Options returns the task options.
func (t *Task) Options() task.Options {
return *t.opts
}

// New creates a new task for the "mvdan.cc/gofumpt" Go module command.
//nolint:gocritic // The app.Config struct is passed as value by design to ensure immutability.
func New(wand wand.Wand, ac app.Config, opts ...Option) *Task {
return &Task{ac: ac, opts: NewOptions(opts...)}
}
147 changes: 147 additions & 0 deletions pkg/task/gofumpt/options.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) 2019-present Sven Greb <development@svengreb.de>
// This source code is licensed under the MIT license found in the LICENSE file.

package gofumpt

import (
"github.com/Masterminds/semver/v3"

"github.com/svengreb/wand/pkg/project"
)

const (
// DefaultGoModulePath is the default module import path.
DefaultGoModulePath = "mvdan.cc/gofumpt"

// TaskName is the name of the task.
TaskName = "gofumpt"
)

// Option is a task option.
type Option func(*Options)

// Options are task options.
type Options struct {
// env is the task specific environment.
env map[string]string

// extraArgs are additional arguments passed to the command.
extraArgs []string

// extraRules indicates whether gofumpt's extra rules should be enabled.
// See https://github.com/mvdan/gofumpt#added-rules for more details about available rules.
extraRules bool

// goModule is the Go module identifier.
goModule *project.GoModuleID

// listNonCompliantFiles indicates whether files, whose formatting are not conform to the style guide, should be
// listed.
listNonCompliantFiles bool

// paths are the paths to search for Go source files.
// By default all directories are scanned recursively starting from the working directory of the current process.
paths []string

// persistChanges indicates whether results are written to the source files instead of standard output.
persistChanges bool

// reportAllErrors indicates whether all errors should be printed instead of only the first 10 on different lines.
reportAllErrors bool

// simplify indicates whether code should be simplified.
simplify bool
}

// NewOptions creates new task options.
func NewOptions(opts ...Option) *Options {
opt := &Options{
env: make(map[string]string),
goModule: &project.GoModuleID{
Path: DefaultGoModulePath,
IsLatest: true,
},
}
for _, o := range opts {
o(opt)
}

return opt
}

// WithEnv sets the task specific environment.
func WithEnv(env map[string]string) Option {
return func(o *Options) {
o.env = env
}
}

// WithExtraArgs sets additional arguments to pass to the command.
func WithExtraArgs(extraArgs ...string) Option {
return func(o *Options) {
o.extraArgs = append(o.extraArgs, extraArgs...)
}
}

// WithExtraRules indicates whether gofumpt's extra rules should be enabled.
// See https://github.com/mvdan/gofumpt#added-rules for more details about available rules.
func WithExtraRules(extraRules bool) Option {
return func(o *Options) {
o.extraRules = extraRules
}
}

// WithListNonCompliantFiles indicates whether files, whose formatting are not conform to the style guide, are listed.
func WithListNonCompliantFiles(listNonCompliantFiles bool) Option {
return func(o *Options) {
o.listNonCompliantFiles = listNonCompliantFiles
}
}

// WithModulePath sets the module import path.
// Defaults to DefaultGoModulePath.
func WithModulePath(path string) Option {
return func(o *Options) {
if path != "" {
o.goModule.Path = path
}
}
}

// WithModuleVersion sets the module version.
func WithModuleVersion(version *semver.Version) Option {
return func(o *Options) {
if version != nil {
o.goModule.Version = version
}
}
}

// WithPaths sets the paths to search for Go source files.
// By default all directories are scanned recursively starting from the working directory of the current process.
func WithPaths(paths ...string) Option {
return func(o *Options) {
o.paths = append(o.paths, paths...)
}
}

// WithPersistedChanges indicates whether results are written to the source files instead of standard output.
func WithPersistedChanges(persistChanges bool) Option {
return func(o *Options) {
o.persistChanges = persistChanges
}
}

// WithReportAllErrors indicates whether all errors should be printed instead of only the first 10 on different lines.
func WithReportAllErrors(reportAllErrors bool) Option {
return func(o *Options) {
o.reportAllErrors = reportAllErrors
}
}

// WithSimplify indicates whether code should be simplified.
func WithSimplify(simplify bool) Option {
return func(o *Options) {
o.simplify = simplify
}
}
2 changes: 1 addition & 1 deletion pkg/task/goimports/goimports.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ func (t *Task) BuildParams() []string {
if len(t.opts.paths) > 0 {
params = append(params, t.opts.paths...)
} else {
// ...or otherwise search recursively starting from the current working directory.
// ...or otherwise search recursively starting from the working directory of the current process.
params = append(params, ".")
}

Expand Down
4 changes: 3 additions & 1 deletion pkg/task/goimports/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import (
const (
// DefaultGoModulePath is the default module import path.
DefaultGoModulePath = "golang.org/x/tools/cmd/goimports"

// TaskName is the name of the task.
TaskName = "goimports"
)

// Option is a task option.
Expand Down Expand Up @@ -104,7 +107,6 @@ func WithModulePath(path string) Option {
}

// WithModuleVersion sets the module version.
// Defaults to DefaultGoModuleVersion.
func WithModuleVersion(version *semver.Version) Option {
return func(o *Options) {
if version != nil {
Expand Down
Loading

0 comments on commit 3273e91

Please sign in to comment.