github.com/jacobsoderblom/buffalo@v0.11.0/buffalo/cmd/updater/imports.go (about)

     1  package updater
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"go/ast"
     7  	"go/parser"
     8  	"go/printer"
     9  	"go/token"
    10  	"io/ioutil"
    11  	"os"
    12  	"path"
    13  	"path/filepath"
    14  	"strconv"
    15  	"strings"
    16  
    17  	"github.com/pkg/errors"
    18  )
    19  
    20  // ImportConverter will changes imports from a -> b
    21  type ImportConverter struct {
    22  	Data map[string]string
    23  }
    24  
    25  // Process will walk all the .go files in an application, excluding ./vendor.
    26  // It will then attempt to convert any old import paths to any new import paths
    27  // used by this version Buffalo.
    28  func (c ImportConverter) Process(r *Runner) error {
    29  	fmt.Println("~~~ Rewriting Imports ~~~")
    30  	err := filepath.Walk(".", func(p string, info os.FileInfo, err error) error {
    31  		for _, n := range []string{"vendor", "node_modules", ".git"} {
    32  			if strings.HasPrefix(p, n+string(filepath.Separator)) {
    33  				return nil
    34  			}
    35  		}
    36  		if info.IsDir() {
    37  			return nil
    38  		}
    39  		ext := filepath.Ext(p)
    40  		if ext == ".go" {
    41  			if err := c.rewriteFile(p); err != nil {
    42  				return errors.WithStack(err)
    43  			}
    44  		}
    45  		return nil
    46  	})
    47  	if err != nil {
    48  		return errors.WithStack(err)
    49  	}
    50  
    51  	if r.App.WithDep {
    52  		b, err := ioutil.ReadFile("Gopkg.toml")
    53  		if err != nil {
    54  			return errors.WithStack(err)
    55  		}
    56  		for k := range c.Data {
    57  			if bytes.Contains(b, []byte(k)) {
    58  				r.Warnings = append(r.Warnings, fmt.Sprintf("Your Gopkg.toml contains the following import that need to be changed MANUALLY: %s", k))
    59  			}
    60  		}
    61  	}
    62  	return nil
    63  }
    64  
    65  // TAKEN FROM https://gist.github.com/jackspirou/61ce33574e9f411b8b4a
    66  // rewriteFile rewrites import statments in the named file
    67  // according to the rules supplied by the map of strings.
    68  func (c ImportConverter) rewriteFile(name string) error {
    69  
    70  	// create an empty fileset.
    71  	fset := token.NewFileSet()
    72  
    73  	// parse the .go file.
    74  	// we are parsing the entire file with comments, so we don't lose anything
    75  	// if we need to write it back out.
    76  	f, err := parser.ParseFile(fset, name, nil, parser.ParseComments)
    77  	if err != nil {
    78  		e := err.Error()
    79  		msg := "expected 'package', found 'EOF'"
    80  		if e[len(e)-len(msg):] == msg {
    81  			return nil
    82  		}
    83  		return err
    84  	}
    85  
    86  	// iterate through the import paths. if a change occurs update bool.
    87  	change := false
    88  	for _, i := range f.Imports {
    89  
    90  		// unquote the import path value.
    91  		path, err := strconv.Unquote(i.Path.Value)
    92  		if err != nil {
    93  			return err
    94  		}
    95  
    96  		// match import path with the given replacement map
    97  		if rpath, ok := c.match(path); ok {
    98  			fmt.Printf("[IMPORT] %s: %s -> %s\n", name, path, rpath)
    99  			i.Path.Value = strconv.Quote(rpath)
   100  			change = true
   101  		}
   102  	}
   103  
   104  	for _, cg := range f.Comments {
   105  		for _, cl := range cg.List {
   106  			if strings.HasPrefix(cl.Text, "// import \"") {
   107  
   108  				// trim off extra comment stuff
   109  				ctext := cl.Text
   110  				ctext = strings.TrimPrefix(ctext, "// import")
   111  				ctext = strings.TrimSpace(ctext)
   112  
   113  				// unquote the comment import path value
   114  				ctext, err := strconv.Unquote(ctext)
   115  				if err != nil {
   116  					return err
   117  				}
   118  
   119  				// match the comment import path with the given replacement map
   120  				if ctext, ok := c.match(ctext); ok {
   121  					cl.Text = "// import " + strconv.Quote(ctext)
   122  					change = true
   123  				}
   124  			}
   125  		}
   126  	}
   127  
   128  	// if no change occured, then we don't need to write to disk, just return.
   129  	if !change {
   130  		return nil
   131  	}
   132  
   133  	// since the imports changed, resort them.
   134  	ast.SortImports(fset, f)
   135  
   136  	// create a temporary file, this easily avoids conflicts.
   137  	temp := name + ".temp"
   138  	w, err := os.Create(temp)
   139  	if err != nil {
   140  		return err
   141  	}
   142  
   143  	// write changes to .temp file, and include proper formatting.
   144  	err = (&printer.Config{Mode: printer.TabIndent | printer.UseSpaces, Tabwidth: 8}).Fprint(w, fset, f)
   145  	if err != nil {
   146  		return err
   147  	}
   148  
   149  	// close the writer
   150  	err = w.Close()
   151  	if err != nil {
   152  		return err
   153  	}
   154  
   155  	// rename the .temp to .go
   156  	return os.Rename(temp, name)
   157  }
   158  
   159  // match takes an import path and replacement map.
   160  func (c ImportConverter) match(importpath string) (string, bool) {
   161  	for key, value := range c.Data {
   162  		if len(importpath) >= len(key) {
   163  			if importpath[:len(key)] == key {
   164  				result := path.Join(value, importpath[len(key):])
   165  				return result, true
   166  			}
   167  		}
   168  	}
   169  	return importpath, false
   170  }