github.com/daixiang0/gci@v0.13.4/pkg/gci/gci.go (about)

     1  package gci
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	goFormat "go/format"
     8  	"os"
     9  	"sync"
    10  
    11  	"github.com/hexops/gotextdiff"
    12  	"github.com/hexops/gotextdiff/myers"
    13  	"github.com/hexops/gotextdiff/span"
    14  	"golang.org/x/sync/errgroup"
    15  
    16  	"github.com/daixiang0/gci/pkg/config"
    17  	"github.com/daixiang0/gci/pkg/format"
    18  	"github.com/daixiang0/gci/pkg/io"
    19  	"github.com/daixiang0/gci/pkg/log"
    20  	"github.com/daixiang0/gci/pkg/parse"
    21  	"github.com/daixiang0/gci/pkg/section"
    22  	"github.com/daixiang0/gci/pkg/utils"
    23  )
    24  
    25  func LocalFlagsToSections(localFlags []string) section.SectionList {
    26  	sections := section.DefaultSections()
    27  	// Add all local arguments as ImportPrefix sections
    28  	// for _, l := range localFlags {
    29  	// 	sections = append(sections, section.Section{l, nil, nil})
    30  	// }
    31  	return sections
    32  }
    33  
    34  func PrintFormattedFiles(paths []string, cfg config.Config) error {
    35  	return processStdInAndGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error {
    36  		fmt.Print(string(formattedFile))
    37  		return nil
    38  	})
    39  }
    40  
    41  func WriteFormattedFiles(paths []string, cfg config.Config) error {
    42  	return processGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error {
    43  		if bytes.Equal(unmodifiedFile, formattedFile) {
    44  			log.L().Debug(fmt.Sprintf("Skipping correctly formatted File: %s", filePath))
    45  			return nil
    46  		}
    47  		log.L().Info(fmt.Sprintf("Writing formatted File: %s", filePath))
    48  		return os.WriteFile(filePath, formattedFile, 0o644)
    49  	})
    50  }
    51  
    52  func ListUnFormattedFiles(paths []string, cfg config.Config) error {
    53  	return processGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error {
    54  		if bytes.Equal(unmodifiedFile, formattedFile) {
    55  			return nil
    56  		}
    57  		fmt.Println(filePath)
    58  		return nil
    59  	})
    60  }
    61  
    62  func DiffFormattedFiles(paths []string, cfg config.Config) error {
    63  	return processStdInAndGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error {
    64  		fileURI := span.URIFromPath(filePath)
    65  		edits := myers.ComputeEdits(fileURI, string(unmodifiedFile), string(formattedFile))
    66  		unifiedEdits := gotextdiff.ToUnified(filePath, filePath, string(unmodifiedFile), edits)
    67  		fmt.Printf("%v", unifiedEdits)
    68  		return nil
    69  	})
    70  }
    71  
    72  func DiffFormattedFilesToArray(paths []string, cfg config.Config, diffs *[]string, lock *sync.Mutex) error {
    73  	log.InitLogger()
    74  	defer log.L().Sync()
    75  	return processStdInAndGoFilesInPaths(paths, cfg, func(filePath string, unmodifiedFile, formattedFile []byte) error {
    76  		fileURI := span.URIFromPath(filePath)
    77  		edits := myers.ComputeEdits(fileURI, string(unmodifiedFile), string(formattedFile))
    78  		unifiedEdits := gotextdiff.ToUnified(filePath, filePath, string(unmodifiedFile), edits)
    79  		lock.Lock()
    80  		*diffs = append(*diffs, fmt.Sprint(unifiedEdits))
    81  		lock.Unlock()
    82  		return nil
    83  	})
    84  }
    85  
    86  type fileFormattingFunc func(filePath string, unmodifiedFile, formattedFile []byte) error
    87  
    88  func processStdInAndGoFilesInPaths(paths []string, cfg config.Config, fileFunc fileFormattingFunc) error {
    89  	return ProcessFiles(io.StdInGenerator.Combine(io.GoFilesInPathsGenerator(paths, cfg.SkipVendor)), cfg, fileFunc)
    90  }
    91  
    92  func processGoFilesInPaths(paths []string, cfg config.Config, fileFunc fileFormattingFunc) error {
    93  	return ProcessFiles(io.GoFilesInPathsGenerator(paths, cfg.SkipVendor), cfg, fileFunc)
    94  }
    95  
    96  func ProcessFiles(fileGenerator io.FileGeneratorFunc, cfg config.Config, fileFunc fileFormattingFunc) error {
    97  	var taskGroup errgroup.Group
    98  	files, err := fileGenerator()
    99  	if err != nil {
   100  		return err
   101  	}
   102  	for _, file := range files {
   103  		// run file processing in parallel
   104  		taskGroup.Go(processingFunc(file, cfg, fileFunc))
   105  	}
   106  	return taskGroup.Wait()
   107  }
   108  
   109  func processingFunc(file io.FileObj, cfg config.Config, formattingFunc fileFormattingFunc) func() error {
   110  	return func() error {
   111  		unmodifiedFile, formattedFile, err := LoadFormatGoFile(file, cfg)
   112  		if err != nil {
   113  			// if errors.Is(err, FileParsingError{}) {
   114  			// 	// do not process files that are improperly formatted
   115  			// 	return nil
   116  			// }
   117  			return err
   118  		}
   119  		return formattingFunc(file.Path(), unmodifiedFile, formattedFile)
   120  	}
   121  }
   122  
   123  func LoadFormatGoFile(file io.FileObj, cfg config.Config) (src, dist []byte, err error) {
   124  	src, err = file.Load()
   125  	log.L().Debug(fmt.Sprintf("Loaded File: %s", file.Path()))
   126  	if err != nil {
   127  		return nil, nil, err
   128  	}
   129  
   130  	return LoadFormat(src, file.Path(), cfg)
   131  }
   132  
   133  func LoadFormat(in []byte, path string, cfg config.Config) (src, dist []byte, err error) {
   134  	src = in
   135  
   136  	if cfg.SkipGenerated && parse.IsGeneratedFileByComment(string(src)) {
   137  		return src, src, nil
   138  	}
   139  
   140  	imports, headEnd, tailStart, cStart, cEnd, err := parse.ParseFile(src, path)
   141  	if err != nil {
   142  		if errors.Is(err, parse.NoImportError{}) {
   143  			return src, src, nil
   144  		}
   145  		return nil, nil, err
   146  	}
   147  
   148  	// do not do format if only one import
   149  	if len(imports) <= 1 {
   150  		return src, src, nil
   151  	}
   152  
   153  	result, err := format.Format(imports, &cfg)
   154  	if err != nil {
   155  		return nil, nil, err
   156  	}
   157  
   158  	firstWithIndex := true
   159  
   160  	var body []byte
   161  
   162  	// order by section list
   163  	for _, s := range cfg.Sections {
   164  		if len(result[s.String()]) > 0 {
   165  			if len(body) > 0 {
   166  				body = append(body, utils.Linebreak)
   167  			}
   168  			for _, d := range result[s.String()] {
   169  				AddIndent(&body, &firstWithIndex)
   170  				body = append(body, src[d.Start:d.End]...)
   171  			}
   172  		}
   173  	}
   174  
   175  	head := make([]byte, headEnd)
   176  	copy(head, src[:headEnd])
   177  	tail := make([]byte, len(src)-tailStart)
   178  	copy(tail, src[tailStart:])
   179  
   180  	// ensure C
   181  	if cStart != 0 {
   182  		head = append(head, src[cStart:cEnd]...)
   183  		head = append(head, utils.Linebreak)
   184  	}
   185  
   186  	// add beginning of import block
   187  	head = append(head, `import (`...)
   188  	head = append(head, utils.Linebreak)
   189  	// add end of import block
   190  	body = append(body, []byte{utils.RightParenthesis, utils.Linebreak}...)
   191  
   192  	log.L().Debug(fmt.Sprintf("head:\n%s", head))
   193  	log.L().Debug(fmt.Sprintf("body:\n%s", body))
   194  	if len(tail) > 20 {
   195  		log.L().Debug(fmt.Sprintf("tail:\n%s", tail[:20]))
   196  	} else {
   197  		log.L().Debug(fmt.Sprintf("tail:\n%s", tail))
   198  	}
   199  
   200  	var totalLen int
   201  	slices := [][]byte{head, body, tail}
   202  	for _, s := range slices {
   203  		totalLen += len(s)
   204  	}
   205  	dist = make([]byte, totalLen)
   206  	var i int
   207  	for _, s := range slices {
   208  		i += copy(dist[i:], s)
   209  	}
   210  
   211  	// remove ^M(\r\n) from Win to Unix
   212  	dist = bytes.ReplaceAll(dist, []byte{utils.WinLinebreak}, []byte{utils.Linebreak})
   213  
   214  	log.L().Debug(fmt.Sprintf("raw:\n%s", dist))
   215  	dist, err = goFormat.Source(dist)
   216  	if err != nil {
   217  		return nil, nil, err
   218  	}
   219  
   220  	return src, dist, nil
   221  }
   222  
   223  func AddIndent(in *[]byte, first *bool) {
   224  	if *first {
   225  		*first = false
   226  		return
   227  	}
   228  	*in = append(*in, utils.Indent)
   229  }