@@ -3,14 +3,16 @@ package main
33import  (
44	"bytes" 
55	"fmt" 
6- 	"io" 
76	"io/fs" 
87	"os" 
98	"path/filepath" 
109	"regexp" 
10+ 	"runtime" 
1111	"strings" 
1212
13+ 	"golang.org/x/sync/errgroup" 
1314	"golang.org/x/tools/imports" 
15+ 	"golang.org/x/xerrors" 
1416)
1517
1618var  (
2527	consecutiveNewlinesRegex  =  regexp .MustCompile (`\n\s*\n` )
2628)
2729
30+ type  fileContent  struct  {
31+ 	path      string 
32+ 	original  []byte 
33+ 	current   []byte 
34+ 	changed   bool 
35+ }
36+ 
2837func  main () {
38+ 	numWorkers  :=  runtime .NumCPU ()
39+ 
40+ 	// Collect all the filenames that we want to process 
41+ 	var  files  []string 
2942	if  err  :=  filepath .Walk ("." , func (path  string , info  fs.FileInfo , err  error ) error  {
3043		switch  {
3144		case  err  !=  nil :
@@ -39,49 +52,112 @@ func main() {
3952			! strings .HasSuffix (info .Name (), ".go" ):
4053			return  nil 
4154		}
42- 		return  fixGoImports (path )
55+ 		files  =  append (files , path )
56+ 		return  nil 
4357	}); err  !=  nil  {
44- 		fmt .Printf ( "Error fixing go imports : %v\n " , err )
58+ 		_ ,  _   =   fmt .Fprintf ( os . Stderr ,  "Error walking directory : %v\n " , err )
4559		os .Exit (1 )
4660	}
47- }
4861
49- func   fixGoImports ( path   string )  error  { 
50- 	sourceFile , err  :=  os . OpenFile ( path ,  os . O_RDWR ,  0666 )
62+ 	 // Read all file contents in parallel 
63+ 	fileContents , err  :=  readFilesParallel ( files ,  numWorkers )
5164	if  err  !=  nil  {
52- 		return  err 
65+ 		_ , _  =  fmt .Fprintf (os .Stderr , "Error reading files: %v\n " , err )
66+ 		os .Exit (1 )
5367	}
54- 	defer  func () { _  =  sourceFile .Close () }()
5568
56- 	source , err  :=  io .ReadAll (sourceFile )
57- 	if  err  !=  nil  {
58- 		return  err 
59- 	}
60- 	formatted  :=  collapseImportNewlines (source )
69+ 	// Because we have multiple ways of separating imports, we have to imports.Process for each one 
70+ 	// but imports.LocalPrefix is a global, so we have to set it for each group and process files 
71+ 	// in parallel. 
6172	for  _ , prefix  :=  range  groupByPrefixes  {
6273		imports .LocalPrefix  =  prefix 
63- 		formatted ,  err  =   imports . Process ( path ,  formatted ,  nil ) 
64- 		if   err   !=   nil  { 
65- 			return   err 
74+ 		if  err  :=   processFilesParallel ( fileContents ,  numWorkers );  err   !=   nil  { 
75+ 			 _ ,  _   =   fmt . Fprintf ( os . Stderr ,  "Error processing files with prefix %s: %v \n " ,  prefix ,  err ) 
76+ 			os . Exit ( 1 ) 
6677		}
6778	}
68- 	if  ! bytes .Equal (source , formatted ) {
69- 		if  err  :=  replaceFileContent (sourceFile , formatted ); err  !=  nil  {
70- 			return  err 
71- 		}
79+ 
80+ 	// Write modified files in parallel 
81+ 	if  err  :=  writeFilesParallel (fileContents , numWorkers ); err  !=  nil  {
82+ 		_ , _  =  fmt .Fprintf (os .Stderr , "Error writing files: %v\n " , err )
83+ 		os .Exit (1 )
7284	}
73- 	return  nil 
7485}
7586
76- func  replaceFileContent (target  * os.File , replacement  []byte ) error  {
77- 	if  _ , err  :=  target .Seek (0 , io .SeekStart ); err  !=  nil  {
78- 		return  err 
87+ func  readFilesParallel (files  []string , numWorkers  int ) ([]* fileContent , error ) {
88+ 	fileContents  :=  make ([]* fileContent , len (files ))
89+ 
90+ 	var  g  errgroup.Group 
91+ 	g .SetLimit (numWorkers )
92+ 
93+ 	for  i , path  :=  range  files  {
94+ 		g .Go (func () error  {
95+ 			content , err  :=  os .ReadFile (path )
96+ 			if  err  !=  nil  {
97+ 				return  xerrors .Errorf ("reading %s: %w" , path , err )
98+ 			}
99+ 
100+ 			// Collapse is a cheap operation to do here 
101+ 			collapsed  :=  collapseImportNewlines (content )
102+ 			fileContents [i ] =  & fileContent {
103+ 				path :     path ,
104+ 				original : content ,
105+ 				current :  collapsed ,
106+ 				changed :  ! bytes .Equal (content , collapsed ),
107+ 			}
108+ 			return  nil 
109+ 		})
79110	}
80- 	written , err  :=  target .Write (replacement )
81- 	if  err  !=  nil  {
82- 		return  err 
111+ 
112+ 	if  err  :=  g .Wait (); err  !=  nil  {
113+ 		return  nil , err 
114+ 	}
115+ 
116+ 	return  fileContents , nil 
117+ }
118+ 
119+ func  processFilesParallel (fileContents  []* fileContent , numWorkers  int ) error  {
120+ 	var  g  errgroup.Group 
121+ 	g .SetLimit (numWorkers )
122+ 
123+ 	for  _ , file  :=  range  fileContents  {
124+ 		if  file  ==  nil  {
125+ 			continue 
126+ 		}
127+ 		g .Go (func () error  {
128+ 			formatted , err  :=  imports .Process (file .path , file .current , nil )
129+ 			if  err  !=  nil  {
130+ 				return  xerrors .Errorf ("processing %s: %w" , file .path , err )
131+ 			}
132+ 
133+ 			if  ! bytes .Equal (file .current , formatted ) {
134+ 				file .current  =  formatted 
135+ 				file .changed  =  true 
136+ 			}
137+ 			return  nil 
138+ 		})
83139	}
84- 	return  target .Truncate (int64 (written ))
140+ 
141+ 	return  g .Wait ()
142+ }
143+ 
144+ func  writeFilesParallel (fileContents  []* fileContent , numWorkers  int ) error  {
145+ 	var  g  errgroup.Group 
146+ 	g .SetLimit (numWorkers )
147+ 
148+ 	for  _ , file  :=  range  fileContents  {
149+ 		if  file  ==  nil  ||  ! file .changed  {
150+ 			continue 
151+ 		}
152+ 		g .Go (func () error  {
153+ 			if  err  :=  os .WriteFile (file .path , file .current , 0666 ); err  !=  nil  {
154+ 				return  xerrors .Errorf ("writing %s: %w" , file .path , err )
155+ 			}
156+ 			return  nil 
157+ 		})
158+ 	}
159+ 
160+ 	return  g .Wait ()
85161}
86162
87163func  collapseImportNewlines (content  []byte ) []byte  {
0 commit comments