github.com/tools/godep@v0.0.0-20180126220526-ce0bfadeb516/rewrite.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"log"
     6  	"os"
     7  	"path/filepath"
     8  	"strconv"
     9  	"strings"
    10  
    11  	"go/ast"
    12  	"go/parser"
    13  	"go/printer"
    14  	"go/token"
    15  
    16  	"github.com/kr/fs"
    17  )
    18  
    19  // rewrite visits the go files in pkgs, plus all go files
    20  // in the directory tree Godeps, rewriting import statements
    21  // according to the rules for func qualify.
    22  func rewrite(pkgs []*Package, qual string, paths []string) error {
    23  	for _, path := range pkgFiles(pkgs) {
    24  		debugln("rewrite", path)
    25  		err := rewriteTree(path, qual, paths)
    26  		if err != nil {
    27  			return err
    28  		}
    29  	}
    30  	return rewriteTree("Godeps", qual, paths)
    31  }
    32  
    33  // pkgFiles returns the full filesystem path to all go files in pkgs.
    34  func pkgFiles(pkgs []*Package) []string {
    35  	var a []string
    36  	for _, pkg := range pkgs {
    37  		for _, s := range pkg.allGoFiles() {
    38  			a = append(a, filepath.Join(pkg.Dir, s))
    39  		}
    40  	}
    41  	return a
    42  }
    43  
    44  // rewriteTree recursively visits the go files in path, rewriting
    45  // import statements according to the rules for func qualify.
    46  // This function ignores the 'testdata' directory.
    47  func rewriteTree(path, qual string, paths []string) error {
    48  	w := fs.Walk(path)
    49  	for w.Step() {
    50  		if w.Err() != nil {
    51  			log.Println("rewrite:", w.Err())
    52  			continue
    53  		}
    54  		s := w.Stat()
    55  		if s.IsDir() && s.Name() == "testdata" {
    56  			w.SkipDir()
    57  			continue
    58  		}
    59  		if strings.HasSuffix(w.Path(), ".go") {
    60  			err := rewriteGoFile(w.Path(), qual, paths)
    61  			if err != nil {
    62  				return err
    63  			}
    64  		}
    65  	}
    66  	return nil
    67  }
    68  
    69  // rewriteGoFile rewrites import statements in the named file
    70  // according to the rules for func qualify.
    71  func rewriteGoFile(name, qual string, paths []string) error {
    72  	debugln("rewriteGoFile", name, ",", qual, ",", paths)
    73  	printerConfig := &printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}
    74  	fset := token.NewFileSet()
    75  	f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	var changed bool
    81  	for _, s := range f.Imports {
    82  		name, err := strconv.Unquote(s.Path.Value)
    83  		if err != nil {
    84  			return err // can't happen
    85  		}
    86  		q := qualify(unqualify(name), qual, paths)
    87  		if q != name {
    88  			s.Path.Value = strconv.Quote(q)
    89  			changed = true
    90  		}
    91  	}
    92  	if !changed {
    93  		return nil
    94  	}
    95  	var buffer bytes.Buffer
    96  	if err = printerConfig.Fprint(&buffer, fset, f); err != nil {
    97  		return err
    98  	}
    99  	fset = token.NewFileSet()
   100  	f, err = parser.ParseFile(fset, name, &buffer, parser.ParseComments)
   101  	if err != nil {
   102  		return err
   103  	}
   104  	ast.SortImports(fset, f)
   105  	tpath := name + ".temp"
   106  	t, err := os.Create(tpath)
   107  	if err != nil {
   108  		return err
   109  	}
   110  	if err = printerConfig.Fprint(t, fset, f); err != nil {
   111  		return err
   112  	}
   113  	if err = t.Close(); err != nil {
   114  		return err
   115  	}
   116  	// This is required before the rename on windows.
   117  	if err = os.Remove(name); err != nil {
   118  		return err
   119  	}
   120  	return os.Rename(tpath, name)
   121  }
   122  
   123  func defaultSep(experiment bool) string {
   124  	if experiment {
   125  		return "/vendor/"
   126  	}
   127  	return "/Godeps/_workspace/src/"
   128  }
   129  
   130  func relativeVendorTarget(experiment bool) string {
   131  	full := defaultSep(experiment)
   132  	if full[0] == '/' {
   133  		full = full[1:]
   134  	}
   135  	return filepath.FromSlash(full)
   136  }
   137  
   138  // unqualify returns the part of importPath after the last
   139  // occurrence of the signature path elements
   140  // (Godeps/_workspace/src) that always precede imported
   141  // packages in rewritten import paths.
   142  //
   143  // For example,
   144  //   unqualify(C)                         = C
   145  //   unqualify(D/Godeps/_workspace/src/C) = C
   146  func unqualify(importPath string) string {
   147  	if i := strings.LastIndex(importPath, sep); i != -1 {
   148  		importPath = importPath[i+len(sep):]
   149  	}
   150  	return importPath
   151  }
   152  
   153  // qualify qualifies importPath with its corresponding import
   154  // path in the Godeps src copy of package pkg. If importPath
   155  // is a directory lexically contained in a path in paths,
   156  // it will be qualified with package pkg; otherwise, it will
   157  // be returned unchanged.
   158  //
   159  // For example, given paths {D, T} and pkg C,
   160  //   importPath  returns
   161  //   C           C
   162  //   fmt         fmt
   163  //   D           C/Godeps/_workspace/src/D
   164  //   D/P         C/Godeps/_workspace/src/D/P
   165  //   T           C/Godeps/_workspace/src/T
   166  func qualify(importPath, pkg string, paths []string) string {
   167  	if containsPathPrefix(paths, importPath) {
   168  		return pkg + sep + importPath
   169  	}
   170  	return importPath
   171  }