88//
99// # Usage
1010//
11- // errtrace [options] <source files>
11+ // errtrace [options] <source files | patterns>
12+ //
13+ // This will transform source files and write them to the standard output.
14+ //
15+ // If instead of source files, Go package patterns are given,
16+ // errtrace will transform all the files that match those patterns.
17+ // For example, 'errtrace ./...' will transform all files in the current
18+ // package and all subpackages.
1219//
13- // This will transform the source files and write them to the standard output.
1420// Use the following flags to control the output:
1521//
1622// -format
@@ -25,6 +31,7 @@ package main
2531
2632import (
2733 "bytes"
34+ "encoding/json"
2835 "errors"
2936 "flag"
3037 "fmt"
@@ -35,6 +42,8 @@ import (
3542 "io"
3643 "log"
3744 "os"
45+ "os/exec"
46+ "path/filepath"
3847 "regexp"
3948 "sort"
4049 "strconv"
@@ -52,10 +61,10 @@ func main() {
5261}
5362
5463type mainParams struct {
55- Write bool // -w
56- List bool // -l
57- Format format // -format
58- Files []string // list of files to process
64+ Write bool // -w
65+ List bool // -l
66+ Format format // -format
67+ Patterns []string // list of files to process
5968
6069 ImplicitStdin bool // whether stdin was picked because there were no args
6170}
@@ -77,7 +86,7 @@ func (p *mainParams) Parse(w io.Writer, args []string) error {
7786 flag := flag .NewFlagSet ("errtrace" , flag .ContinueOnError )
7887 flag .SetOutput (w )
7988 flag .Usage = func () {
80- fmt .Fprintln (w , "usage: errtrace [options] <source files>" )
89+ fmt .Fprintln (w , "usage: errtrace [options] <source files | patterns >" )
8190 flag .PrintDefaults ()
8291 }
8392
@@ -87,16 +96,17 @@ func (p *mainParams) Parse(w io.Writer, args []string) error {
8796 "write result to the given source files instead of stdout." )
8897 flag .BoolVar (& p .List , "l" , false ,
8998 "list files that would be modified without making any changes." )
99+
90100 // TODO: toolexec mode
91101
92102 if err := flag .Parse (args ); err != nil {
93103 return err
94104 }
95105
96- p .Files = flag .Args ()
97- if len (p .Files ) == 0 {
106+ p .Patterns = flag .Args ()
107+ if len (p .Patterns ) == 0 {
98108 // Read file from stdin when there's no args, similar to gofmt.
99- p .Files = []string {"-" }
109+ p .Patterns = []string {"-" }
100110 p .ImplicitStdin = true
101111 }
102112
@@ -178,28 +188,147 @@ func (cmd *mainCmd) Run(args []string) (exitCode int) {
178188 return 1
179189 }
180190
181- for _ , file := range p .Files {
191+ files , err := expandPatterns (p .Patterns )
192+ if err != nil {
193+ cmd .log .Println ("errtrace:" , err )
194+ return 1
195+ }
196+
197+ // Paths will be printed relative to CWD.
198+ // Paths outside it will be printed as-is.
199+ var workDir string
200+ if wd , err := os .Getwd (); err == nil {
201+ workDir = wd + string (filepath .Separator )
202+ }
203+
204+ for _ , file := range files {
205+ display := file
206+ if workDir != "" {
207+ // Not using filepath.Rel
208+ // because we don't want any ".."s in the path.
209+ display = strings .TrimPrefix (file , workDir )
210+ }
211+ if display == "-" {
212+ display = "stdin"
213+ }
214+
182215 req := fileRequest {
183216 Format : p .shouldFormat (),
184217 Write : p .Write ,
185218 List : p .List ,
186- Filename : file ,
219+ Filename : display ,
220+ Filepath : file ,
187221 ImplicitStdin : p .ImplicitStdin ,
188222 }
189223 if err := cmd .processFile (req ); err != nil {
190- cmd .log .Printf ("%s:%s" , file , err )
224+ cmd .log .Printf ("%s:%s" , display , err )
191225 exitCode = 1
192226 }
193227 }
194228
195229 return exitCode
196230}
197231
232+ // expandPatterns turns the given list of patterns and files
233+ // into a list of paths to files.
234+ //
235+ // Arguments that are already files are returned as-is.
236+ // Arguments that are patterns are expanded using 'go list'.
237+ // As a special case for stdin, "-" is returned as-is.
238+ func expandPatterns (args []string ) ([]string , error ) {
239+ var files , patterns []string
240+ for _ , arg := range args {
241+ if arg == "-" {
242+ files = append (files , arg )
243+ continue
244+ }
245+
246+ if info , err := os .Stat (arg ); err == nil && ! info .IsDir () {
247+ files = append (files , arg )
248+ continue
249+ }
250+
251+ patterns = append (patterns , arg )
252+ }
253+
254+ if len (patterns ) > 0 {
255+ pkgFiles , err := goListFiles (patterns )
256+ if err != nil {
257+ return nil , fmt .Errorf ("go list: %w" , err )
258+ }
259+
260+ files = append (files , pkgFiles ... )
261+ }
262+
263+ return files , nil
264+ }
265+
266+ var _execCommand = exec .Command
267+
268+ func goListFiles (patterns []string ) (files []string , err error ) {
269+ // The -e flag makes 'go list' include erroneous packages.
270+ // This will even include packages that have all files excluded
271+ // by build constraints if explicitly requested.
272+ // (with "path/to/pkg" instead of "./...")
273+ args := []string {"list" , "-find" , "-e" , "-json" }
274+ args = append (args , patterns ... )
275+
276+ var stderr bytes.Buffer
277+ cmd := _execCommand ("go" , args ... )
278+ cmd .Stderr = & stderr
279+
280+ stdout , err := cmd .StdoutPipe ()
281+ if err != nil {
282+ return nil , fmt .Errorf ("create stdout pipe: %w" , err )
283+ }
284+
285+ if err := cmd .Start (); err != nil {
286+ return nil , fmt .Errorf ("start command: %w" , err )
287+ }
288+
289+ type packageInfo struct {
290+ Dir string
291+ GoFiles []string
292+ CgoFiles []string
293+ TestGoFiles []string
294+ XTestGoFiles []string
295+ IgnoredGoFiles []string
296+ }
297+
298+ decoder := json .NewDecoder (stdout )
299+ for decoder .More () {
300+ var pkg packageInfo
301+ if err := decoder .Decode (& pkg ); err != nil {
302+ return nil , fmt .Errorf ("output malformed: %v" , err )
303+ }
304+
305+ for _ , pkgFiles := range [][]string {
306+ pkg .GoFiles ,
307+ pkg .CgoFiles ,
308+ pkg .TestGoFiles ,
309+ pkg .XTestGoFiles ,
310+ pkg .IgnoredGoFiles ,
311+ } {
312+ for _ , f := range pkgFiles {
313+ files = append (files , filepath .Join (pkg .Dir , f ))
314+ }
315+ }
316+ }
317+
318+ if err := cmd .Wait (); err != nil {
319+ return nil , fmt .Errorf ("%w\n %s" , err , stderr .String ())
320+ }
321+
322+ return files , nil
323+ }
324+
198325type fileRequest struct {
199- Format bool
200- Write bool
201- List bool
202- Filename string
326+ Format bool
327+ Write bool
328+ List bool
329+
330+ Filename string // name displayed to the user
331+ Filepath string // actual location on disk, or "-" for stdin
203332
204333 ImplicitStdin bool
205334}
@@ -432,7 +561,7 @@ func (cmd *mainCmd) processFile(r fileRequest) error {
432561var _stdinWait = 200 * time .Millisecond
433562
434563func (cmd * mainCmd ) readFile (r fileRequest ) ([]byte , error ) {
435- if r .Filename != "-" {
564+ if r .Filepath != "-" {
436565 return os .ReadFile (r .Filename )
437566 }
438567
0 commit comments