github.com/torresashjian/cli@v0.10.1-0.20210916231452-89080fe7069c/util/mod.go (about)

     1  package util
     2  
     3  import (
     4  	"bufio"
     5  	"bytes"
     6  	"encoding/json"
     7  	"fmt"
     8  	"io/ioutil"
     9  	"log"
    10  	"net/http"
    11  	"os"
    12  	"os/exec"
    13  	"path/filepath"
    14  	"regexp"
    15  	"strings"
    16  
    17  	"github.com/msoap/byline"
    18  )
    19  
    20  type DepManager interface {
    21  	Init() error
    22  	AddDependency(flogoImport Import) error
    23  	GetPath(flogoImport Import) (string, error)
    24  	AddReplacedContribForBuild() error
    25  	InstallReplacedPkg(string, string) error
    26  	GetAllImports() (map[string]Import, error)
    27  }
    28  
    29  func NewDepManager(sourceDir string) DepManager {
    30  	return &ModDepManager{srcDir: sourceDir, localMods: make(map[string]string)}
    31  }
    32  
    33  type ModDepManager struct {
    34  	srcDir    string
    35  	localMods map[string]string
    36  }
    37  
    38  func (m *ModDepManager) Init() error {
    39  
    40  	err := ExecCmd(exec.Command("go", "mod", "init", "main"), m.srcDir)
    41  	if err == nil {
    42  		return err
    43  	}
    44  
    45  	return nil
    46  }
    47  
    48  func (m *ModDepManager) AddDependency(flogoImport Import) error {
    49  
    50  	// todo: optimize the following
    51  
    52  	// use "go mod edit" (instead of "go get") as first method
    53  	err := ExecCmd(exec.Command("go", "mod", "edit", "-require", flogoImport.GoModImportPath()), m.srcDir)
    54  	if err != nil {
    55  		return err
    56  	}
    57  
    58  
    59  	err = ExecCmd(exec.Command("go", "mod", "verify"), m.srcDir)
    60  	if err == nil {
    61  		err = ExecCmd(exec.Command("go", "mod", "download", flogoImport.ModulePath()), m.srcDir)
    62  	}
    63  
    64  	if err != nil {
    65  		// if the resolution fails and the Flogo import is "classic"
    66  		// (meaning it does not separate module path from Go import path):
    67  		// 1. remove the import manually ("go mod edit -droprequire") would fail
    68  		// 2. try with "go get" instead
    69  		if flogoImport.IsClassic() {
    70  			m.RemoveImport(flogoImport)
    71  
    72  			err = ExecCmd(exec.Command("go", "get", flogoImport.GoGetImportPath()), m.srcDir)
    73  		}
    74  	}
    75  
    76  	if err != nil {
    77  		return err
    78  	}
    79  
    80  	return nil
    81  }
    82  
    83  // GetPath gets the path of where the
    84  func (m *ModDepManager) GetPath(flogoImport Import) (string, error) {
    85  
    86  	currentDir, err := os.Getwd()
    87  	if err != nil {
    88  		return "", err
    89  	}
    90  
    91  	pkg := flogoImport.ModulePath()
    92  
    93  	path, ok := m.localMods[pkg]
    94  	if ok && path != "" {
    95  
    96  		return path, nil
    97  	}
    98  	defer os.Chdir(currentDir)
    99  
   100  	os.Chdir(m.srcDir)
   101  
   102  	file, err := os.Open(filepath.Join(m.srcDir, "go.mod"))
   103  	defer file.Close()
   104  
   105  	var pathForPartial string
   106  
   107  	scanner := bufio.NewScanner(file)
   108  	for scanner.Scan() {
   109  
   110  		line := scanner.Text()
   111  		reqComponents := strings.Fields(line)
   112  		//It is the line in the go.mod which is not useful, so ignore.
   113  		if len(reqComponents) < 2 || (reqComponents[0] == "require" && reqComponents[1] == "(") {
   114  			continue
   115  		}
   116  
   117  		//typically package is 1st component and  version is the 2nd component
   118  		reqPkg := reqComponents[0]
   119  		version := reqComponents[1]
   120  		if reqComponents[0] == "require" {
   121  			//starts with require, so package is 2nd component and  version is the 3rd component
   122  			reqPkg = reqComponents[1]
   123  			version = reqComponents[2]
   124  		}
   125  
   126  		if strings.HasPrefix(pkg, reqPkg) {
   127  
   128  			hasFull := strings.Contains(line, pkg)
   129  			tempPath := strings.Split(reqPkg, "/")
   130  
   131  			tempPath = toLower(tempPath)
   132  			lastIdx := len(tempPath) - 1
   133  
   134  			tempPath[lastIdx] = tempPath[lastIdx] + "@" + version
   135  
   136  			pkgPath := filepath.Join(tempPath...)
   137  
   138  			if !hasFull {
   139  				remaining := pkg[len(reqPkg):]
   140  				tempPath = strings.Split(remaining, "/")
   141  				remainingPath := filepath.Join(tempPath...)
   142  
   143  				pathForPartial = filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", pkgPath, remainingPath)
   144  			} else {
   145  				return filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", pkgPath, flogoImport.RelativeImportPath()), nil
   146  			}
   147  		}
   148  	}
   149  	return pathForPartial, nil
   150  }
   151  
   152  func (m *ModDepManager) RemoveImport(flogoImport Import) error {
   153  
   154  	currentDir, err := os.Getwd()
   155  	if err != nil {
   156  		return err
   157  	}
   158  
   159  	modulePath := flogoImport.ModulePath()
   160  
   161  	defer os.Chdir(currentDir)
   162  
   163  	os.Chdir(m.srcDir)
   164  
   165  	file, err := os.Open(filepath.Join(m.srcDir, "go.mod"))
   166  	if err != nil {
   167  		return err
   168  	}
   169  	defer file.Close()
   170  
   171  	modulePath = strings.Replace(modulePath, "/", "\\/", -1)
   172  	modulePath = strings.Replace(modulePath, ".", "\\.", -1)
   173  	importRegex := regexp.MustCompile(`\s*` + modulePath + `\s+` + flogoImport.Version() + `.*`)
   174  
   175  	lr := byline.NewReader(file)
   176  
   177  	lr.MapString(func(line string) string {
   178  		if importRegex.MatchString(line) {
   179  			return ""
   180  		} else {
   181  			return line
   182  		}
   183  	})
   184  
   185  	updatedGoMod, err := lr.ReadAll()
   186  	if err != nil {
   187  		return err
   188  	}
   189  
   190  	file, err = os.Create(filepath.Join(m.srcDir, "go.mod"))
   191  	if err != nil {
   192  		return err
   193  	}
   194  	defer file.Close()
   195  
   196  	file.Write(updatedGoMod)
   197  
   198  	return nil
   199  }
   200  func (m *ModDepManager) GetAllImports() (map[string]Import, error) {
   201  	file, err := ioutil.ReadFile(filepath.Join(m.srcDir, "go.mod"))
   202  	if err != nil {
   203  		return nil, err
   204  	}
   205  
   206  	content := string(file)
   207  
   208  	imports := strings.Split(content[strings.Index(content, "(")+1:strings.Index(content, ")")], "\n")
   209  	result := make(map[string]Import)
   210  
   211  	for _, pkg := range imports {
   212  		if pkg != " " && pkg != "" {
   213  
   214  			mods := strings.Split(strings.TrimSpace(pkg), " ")
   215  
   216  			modImport, err := ParseImport(strings.Join(mods[:2], "@"))
   217  			if err != nil {
   218  				return nil, err
   219  			}
   220  
   221  			result[modImport.GoImportPath()] = modImport
   222  		}
   223  	}
   224  
   225  	return result, nil
   226  }
   227  
   228  //This function converts capotal letters in package name
   229  // to !(smallercase). Eg C => !c . As this is the way
   230  // go.mod saves every repository in the $GOPATH/pkg/mod.
   231  func toLower(s []string) []string {
   232  	result := make([]string, len(s))
   233  	for i := 0; i < len(s); i++ {
   234  		var b bytes.Buffer
   235  		for _, c := range s[i] {
   236  			if c >= 65 && c <= 90 {
   237  				b.WriteRune(33)
   238  				b.WriteRune(c + 32)
   239  			} else {
   240  				b.WriteRune(c)
   241  			}
   242  		}
   243  		result[i] = b.String()
   244  	}
   245  	return result
   246  }
   247  
   248  var verbose = false
   249  
   250  func SetVerbose(enable bool) {
   251  	verbose = enable
   252  }
   253  
   254  func Verbose() bool {
   255  	return verbose
   256  }
   257  
   258  func ExecCmd(cmd *exec.Cmd, workingDir string) error {
   259  
   260  	if workingDir != "" {
   261  		cmd.Dir = workingDir
   262  	}
   263  
   264  	var out bytes.Buffer
   265  
   266  	if verbose {
   267  		cmd.Stdout = os.Stdout
   268  		cmd.Stderr = os.Stderr
   269  	} else {
   270  		cmd.Stdout = nil
   271  		cmd.Stderr = &out
   272  	}
   273  
   274  	err := cmd.Run()
   275  
   276  	if err != nil {
   277  		return fmt.Errorf(string(out.Bytes()))
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  func (m *ModDepManager) AddReplacedContribForBuild() error {
   284  
   285  	err := ExecCmd(exec.Command("go", "mod", "download"), m.srcDir)
   286  	if err != nil {
   287  		return err
   288  	}
   289  
   290  	text, err := ioutil.ReadFile(filepath.Join(m.srcDir, "go.mod"))
   291  	if err != nil {
   292  		return err
   293  	}
   294  
   295  	data := string(text)
   296  
   297  	index := strings.Index(data, "replace")
   298  	if index != -1 {
   299  		localModules := strings.Split(data[index-1:], "\n")
   300  
   301  		for _, val := range localModules {
   302  			if val != "" {
   303  				mods := strings.Split(val, " ")
   304  				//If the length of mods is more than 4 it contains the versions of package
   305  				//so it is stating to use different version of pkg rather than
   306  				// the local pkg.
   307  				if len(mods) < 5 {
   308  
   309  					m.localMods[mods[1]] = mods[3]
   310  				} else {
   311  
   312  					m.localMods[mods[1]] = filepath.Join(os.Getenv("GOPATH"), "pkg", "mod", mods[3]+"@"+mods[4])
   313  				}
   314  
   315  			}
   316  
   317  		}
   318  		return nil
   319  	}
   320  	return nil
   321  }
   322  
   323  func (m *ModDepManager) InstallReplacedPkg(pkg1 string, pkg2 string) error {
   324  
   325  	m.localMods[pkg1] = pkg2
   326  
   327  	f, err := os.OpenFile(filepath.Join(m.srcDir, "go.mod"), os.O_APPEND|os.O_WRONLY, 0777)
   328  	if err != nil {
   329  		return err
   330  	}
   331  	defer f.Close()
   332  
   333  	if _, err = f.WriteString(fmt.Sprintf("replace %v => %v", pkg1, pkg2)); err != nil {
   334  		return err
   335  	}
   336  
   337  	err = ExecCmd(exec.Command("go", "mod", "download"), m.srcDir)
   338  	if err != nil {
   339  		return err
   340  	}
   341  	return nil
   342  }
   343  
   344  type Resp struct {
   345  	Name string `json:"name"`
   346  }
   347  
   348  func getLatestVersion(path string) string {
   349  
   350  	//To get the latest version number use the  GitHub API.
   351  	resp, err := http.Get("https://api.github.com/repos/TIBCOSoftware/flogo-contrib/releases/latest")
   352  	if err != nil {
   353  		log.Fatalln(err)
   354  	}
   355  
   356  	body, err := ioutil.ReadAll(resp.Body)
   357  	if err != nil {
   358  		log.Fatalln(err)
   359  	}
   360  
   361  	var result Resp
   362  
   363  	err = json.Unmarshal(body, &result)
   364  	if err != nil {
   365  		return ""
   366  	}
   367  
   368  	return result.Name
   369  
   370  }