Skip to content

Commit 73cde23

Browse files
authored
Implement customizable formatting (#43)
* Update standardPackages for Go 1.17.5 Signed-off-by: Norman Gehrsitz <[email protected]> * Update Go version Signed-off-by: Norman Gehrsitz <[email protected]> * Implement configurable Section based formatting logic. The CLI has been built with Cobra in a backwards compatible manner. Signed-off-by: Norman Gehrsitz <[email protected]> * Add tests for file reformatting Signed-off-by: Norman Gehrsitz <[email protected]> * Add test for skipping over malformed files Signed-off-by: Norman Gehrsitz <[email protected]>
1 parent c86b884 commit 73cde23

File tree

104 files changed

+3316
-531
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

104 files changed

+3316
-531
lines changed

README.md

+73-64
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
# GCI
22

3-
GCI, a tool that control golang package import order and make it always deterministic.
3+
GCI, a tool that controls golang package import order and makes it always deterministic.
44

5-
It handles empty lines more smartly than `goimport` does.
5+
The desired output format is highly configurable and allows for more custom formatting than `goimport` does.
66

77
## Download
88

@@ -11,19 +11,81 @@ $ go get github.com/daixiang0/gci
1111
```
1212

1313
## Usage
14+
GCI supports three modes of operation
15+
```shell
16+
$ gci print -h
17+
Print outputs the formatted file. If you want to apply the changes to a file use write instead!
18+
19+
Usage:
20+
gci print path... [flags]
21+
22+
Aliases:
23+
print, output
24+
25+
Flags:
26+
--NoInlineComments Drops inline comments while formatting
27+
--NoPrefixComments Drops comment lines above an import statement while formatting
28+
-s, --Section strings Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). A section can contain a Prefix and a Suffix section which is delimited by ":". These sections can be used for formatting and will only be rendered if the main section contains an entry.
29+
Comment(your text here) | CommentLine(your text here) - Prints the specified indented comment
30+
Def | Default - Contains all imports that could not be matched to another section type
31+
NL | NewLine - Prints an empty line
32+
Prefix(gitlab.com/myorg) | pkgPrefix(gitlab.com/myorg) - Groups all imports with the specified Prefix. Imports will be matched to the longest Prefix.
33+
Std | Standard - Captures all standard packages if they do not match another section
34+
(default [Standard,Default])
35+
-x, --SectionSeparator strings SectionSeparators are inserted between Sections (default [NewLine])
36+
-h, --help help for print
37+
```
38+
39+
```shell
40+
$ gci write -h
41+
Write modifies the specified files in-place
42+
43+
Usage:
44+
gci write path... [flags]
45+
46+
Aliases:
47+
write, overwrite
48+
49+
Flags:
50+
--NoInlineComments Drops inline comments while formatting
51+
--NoPrefixComments Drops comment lines above an import statement while formatting
52+
-s, --Section strings Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). A section can contain a Prefix and a Suffix section which is delimited by ":". These sections can be used for formatting and will only be rendered if the main section contains an entry.
53+
Comment(your text here) | CommentLine(your text here) - Prints the specified indented comment
54+
Def | Default - Contains all imports that could not be matched to another section type
55+
NL | NewLine - Prints an empty line
56+
Prefix(gitlab.com/myorg) | pkgPrefix(gitlab.com/myorg) - Groups all imports with the specified Prefix. Imports will be matched to the longest Prefix.
57+
Std | Standard - Captures all standard packages if they do not match another section
58+
(default [Standard,Default])
59+
-x, --SectionSeparator strings SectionSeparators are inserted between Sections (default [NewLine])
60+
-h, --help help for write
61+
```
1462
1563
```shell
16-
$ gci -h
17-
usage: gci [flags] [path ...]
18-
-d display diffs instead of rewriting files
19-
-local string
20-
put imports beginning with this string after 3rd-party packages, only support one string
21-
-w write result to (source) file instead of stdout
64+
$ gci diff -h
65+
Diff prints a patch in the style of the diff tool that contains the required changes to the file to make it adhere to the specified formatting.
66+
67+
Usage:
68+
gci diff path... [flags]
69+
70+
Flags:
71+
--NoInlineComments Drops inline comments while formatting
72+
--NoPrefixComments Drops comment lines above an import statement while formatting
73+
-s, --Section strings Sections define how inputs will be processed. Section names are case-insensitive and may contain parameters in (). A section can contain a Prefix and a Suffix section which is delimited by ":". These sections can be used for formatting and will only be rendered if the main section contains an entry.
74+
Comment(your text here) | CommentLine(your text here) - Prints the specified indented comment
75+
Def | Default - Contains all imports that could not be matched to another section type
76+
NL | NewLine - Prints an empty line
77+
Prefix(gitlab.com/myorg) | pkgPrefix(gitlab.com/myorg) - Groups all imports with the specified Prefix. Imports will be matched to the longest Prefix.
78+
Std | Standard - Captures all standard packages if they do not match another section
79+
(default [Standard,Default])
80+
-x, --SectionSeparator strings SectionSeparators are inserted between Sections (default [NewLine])
81+
-d, --debug Enables debug output from the formatter
82+
-h, --help help for diff
2283
```
84+
Support for the old CLI style is still present if you do not specify the subcommands. The only difference is that `--local` requires two dashes now.
2385
2486
## Examples
2587
26-
Run `gci -w -local github.com/daixiang0/gci main.go` and you will handle following cases.
88+
Run `gci write --Section Standard --Section Default --Section "Prefix(github.com/daixiang0/gci)" main.go` and you will handle following cases:
2789
2890
### simple case
2991
@@ -58,76 +120,23 @@ package main
58120
import (
59121
"fmt"
60122
go "github.com/golang"
61-
"github.com/daixiang0"
62-
)
63-
```
64-
65-
to
66-
67-
```go
68-
package main
69-
import (
70-
"fmt"
71-
72-
go "github.com/golang"
73-
74-
"github.com/daixiang0/gci"
75-
)
76-
```
77-
78-
### with comment and alias
79-
80-
```go
81-
package main
82-
import (
83-
"fmt"
84-
_ "github.com/golang" // golang
85-
"github.com/daixiang0"
86-
)
87-
```
88-
89-
to
90-
91-
```go
92-
package main
93-
import (
94-
"fmt"
95-
96-
// golang
97-
_ "github.com/golang"
98-
99123
"github.com/daixiang0/gci"
100124
)
101125
```
102126
103-
### with above comment and alias
104-
105-
```go
106-
package main
107-
import (
108-
"fmt"
109-
// golang
110-
_ "github.com/golang"
111-
"github.com/daixiang0"
112-
)
113-
```
114-
115127
to
116128
117129
```go
118130
package main
119131
import (
120132
"fmt"
121133
122-
// golang
123-
_ "github.com/golang"
134+
go "github.com/golang"
124135
125136
"github.com/daixiang0/gci"
126137
)
127138
```
128139
129140
## TODO
130141
131-
- Support multi-3rd-party packages
132-
- Support multiple lines of comment in import block
133-
- Add testcases
142+
- Add more testcases

cmd/gci/completion.go

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package gci
2+
3+
import (
4+
"strings"
5+
6+
"github.com/spf13/cobra"
7+
)
8+
9+
func subCommandOrGoFileCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
10+
var commandAliases []string
11+
for _, subCmd := range cmd.Commands() {
12+
commandAliases = append(commandAliases, subCmd.Name())
13+
commandAliases = append(commandAliases, subCmd.Aliases...)
14+
}
15+
for _, subCmdStr := range commandAliases {
16+
if strings.HasPrefix(subCmdStr, toComplete) {
17+
// completion for commands is already provided by cobra
18+
// do not return file completion
19+
return []string{}, cobra.ShellCompDirectiveNoFileComp
20+
}
21+
}
22+
return goFileCompletion(cmd, args, toComplete)
23+
}
24+
func goFileCompletion(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
25+
return []string{"go"}, cobra.ShellCompDirectiveFilterFileExt
26+
}

cmd/gci/diff.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package gci
2+
3+
import (
4+
"github.com/daixiang0/gci/pkg/gci"
5+
)
6+
7+
// diffCmd represents the diff command
8+
func (e *Executor) initDiff() {
9+
e.newGciCommand(
10+
"diff path...",
11+
"Prints a git style diff to STDOUT",
12+
"Diff prints a patch in the style of the diff tool that contains the required changes to the file to make it adhere to the specified formatting.",
13+
[]string{},
14+
true,
15+
gci.DiffFormattedFiles)
16+
}

cmd/gci/gcicommand.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package gci
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/daixiang0/gci/pkg/configuration"
7+
"github.com/daixiang0/gci/pkg/constants"
8+
"github.com/daixiang0/gci/pkg/gci"
9+
sectionsPkg "github.com/daixiang0/gci/pkg/gci/sections"
10+
11+
"github.com/spf13/cobra"
12+
)
13+
14+
type processingFunc = func(args []string, gciCfg gci.GciConfiguration) error
15+
16+
func (e *Executor) newGciCommand(use, short, long string, aliases []string, stdInSupport bool, processingFunc processingFunc) *cobra.Command {
17+
var noInlineComments, noPrefixComments, debug *bool
18+
var sectionStrings, sectionSeparatorStrings *[]string
19+
cmd := cobra.Command{
20+
Use: use,
21+
Aliases: aliases,
22+
Short: short,
23+
Long: long,
24+
ValidArgsFunction: goFileCompletion,
25+
RunE: func(cmd *cobra.Command, args []string) error {
26+
fmtCfg := configuration.FormatterConfiguration{*noInlineComments, *noPrefixComments, *debug}
27+
gciCfg, err := gci.GciStringConfiguration{fmtCfg, *sectionStrings, *sectionSeparatorStrings}.Parse()
28+
if err != nil {
29+
return err
30+
}
31+
return processingFunc(args, *gciCfg)
32+
},
33+
}
34+
if !stdInSupport {
35+
cmd.Args = cobra.MinimumNArgs(1)
36+
}
37+
38+
// register command as subcommand
39+
e.rootCmd.AddCommand(&cmd)
40+
41+
sectionHelp := "Sections define how inputs will be processed. " +
42+
"Section names are case-insensitive and may contain parameters in (). " +
43+
fmt.Sprintf("A section can contain a Prefix and a Suffix section which is delimited by %q. ", constants.SectionSeparator) +
44+
"These sections can be used for formatting and will only be rendered if the main section contains an entry." +
45+
"\n" +
46+
sectionsPkg.SectionParserInst.SectionHelpTexts()
47+
// add flags
48+
debug = cmd.Flags().BoolP("debug", "d", false, "Enables debug output from the formatter")
49+
noInlineComments = cmd.Flags().Bool("NoInlineComments", false, "Drops inline comments while formatting")
50+
noPrefixComments = cmd.Flags().Bool("NoPrefixComments", false, "Drops comment lines above an import statement while formatting")
51+
sectionStrings = cmd.Flags().StringSliceP("Section", "s", gci.DefaultSections().String(), sectionHelp)
52+
sectionSeparatorStrings = cmd.Flags().StringSliceP("SectionSeparator", "x", gci.DefaultSectionSeparators().String(), "SectionSeparators are inserted between Sections")
53+
return &cmd
54+
}

cmd/gci/print.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package gci
2+
3+
import (
4+
"github.com/daixiang0/gci/pkg/gci"
5+
)
6+
7+
// printCmd represents the print command
8+
func (e *Executor) initPrint() {
9+
e.newGciCommand(
10+
"print path...",
11+
"Outputs the formatted file to STDOUT",
12+
"Print outputs the formatted file. If you want to apply the changes to a file use write instead!",
13+
[]string{"output"},
14+
true,
15+
gci.PrintFormattedFiles)
16+
}

cmd/gci/root.go

+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package gci
2+
3+
import (
4+
"fmt"
5+
"os"
6+
7+
"github.com/daixiang0/gci/pkg/configuration"
8+
"github.com/daixiang0/gci/pkg/gci"
9+
10+
"github.com/spf13/cobra"
11+
)
12+
13+
type Executor struct {
14+
rootCmd *cobra.Command
15+
diffMode *bool
16+
writeMode *bool
17+
localFlags *[]string
18+
}
19+
20+
func NewExecutor(version string) *Executor {
21+
e := Executor{}
22+
rootCmd := cobra.Command{
23+
Use: "gci [-diff | -write] [-local localPackageURLs] path...",
24+
Short: "Gci controls golang package import order and makes it always deterministic",
25+
Long: "Gci enables automatic formatting of imports in a deterministic manner" +
26+
"\n" +
27+
"If you want to integrate this as part of your CI take a look at golangci-lint.",
28+
ValidArgsFunction: subCommandOrGoFileCompletion,
29+
Args: cobra.MinimumNArgs(1),
30+
Version: version,
31+
RunE: e.runInCompatibilityMode,
32+
}
33+
e.rootCmd = &rootCmd
34+
e.diffMode = rootCmd.Flags().BoolP("diff", "d", false, "display diffs instead of rewriting files")
35+
e.writeMode = rootCmd.Flags().BoolP("write", "w", false, "write result to (source) file instead of stdout")
36+
e.localFlags = rootCmd.Flags().StringSliceP("local", "l", []string{}, "put imports beginning with this string after 3rd-party packages, separate imports by comma")
37+
e.initDiff()
38+
e.initPrint()
39+
e.initWrite()
40+
return &e
41+
}
42+
43+
// Execute adds all child commands to the root command and sets flags appropriately.
44+
// This is called by main.main(). It only needs to happen once to the rootCmd.
45+
func (e *Executor) Execute() error {
46+
return e.rootCmd.Execute()
47+
}
48+
49+
func (e *Executor) runInCompatibilityMode(cmd *cobra.Command, args []string) error {
50+
// Workaround since the Deprecation message in Cobra can not be printed to STDERR
51+
_, _ = fmt.Fprintln(os.Stderr, "Using the old parameters is deprecated, please use the named subcommands!")
52+
53+
if *e.writeMode && *e.diffMode {
54+
return fmt.Errorf("diff and write must not be specified at the same time")
55+
}
56+
// generate section specification from old localFlags format
57+
sections := gci.LocalFlagsToSections(*e.localFlags)
58+
sectionSeparators := gci.DefaultSectionSeparators()
59+
cfg := gci.GciConfiguration{configuration.FormatterConfiguration{false, false, false}, sections, sectionSeparators}
60+
if *e.writeMode {
61+
return gci.WriteFormattedFiles(args, cfg)
62+
}
63+
if *e.diffMode {
64+
return gci.DiffFormattedFiles(args, cfg)
65+
}
66+
return gci.PrintFormattedFiles(args, cfg)
67+
}

cmd/gci/write.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package gci
2+
3+
import (
4+
"github.com/daixiang0/gci/pkg/gci"
5+
)
6+
7+
// writeCmd represents the write command
8+
func (e *Executor) initWrite() {
9+
e.newGciCommand(
10+
"write path...",
11+
"Formats the specified files in-place",
12+
"Write modifies the specified files in-place",
13+
[]string{"overwrite"},
14+
false,
15+
gci.WriteFormattedFiles)
16+
}

0 commit comments

Comments
 (0)