github.com/cloudwego/kitex@v0.9.0/tool/internal_pkg/util/util.go (about)

     1  // Copyright 2021 CloudWeGo Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //   http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package util
    16  
    17  import (
    18  	"fmt"
    19  	"go/build"
    20  	"go/format"
    21  	"io/ioutil"
    22  	"net/http"
    23  	"os"
    24  	"os/exec"
    25  	"os/user"
    26  	"path/filepath"
    27  	"regexp"
    28  	"runtime"
    29  	"strings"
    30  	"unicode"
    31  
    32  	"github.com/cloudwego/kitex/tool/internal_pkg/log"
    33  )
    34  
    35  // StringSlice implements the flag.Value interface on string slices
    36  // to allow a flag to be set multiple times.
    37  type StringSlice []string
    38  
    39  func (ss *StringSlice) String() string {
    40  	return fmt.Sprintf("%v", *ss)
    41  }
    42  
    43  // Set implements the flag.Value interface.
    44  func (ss *StringSlice) Set(value string) error {
    45  	*ss = append(*ss, value)
    46  	return nil
    47  }
    48  
    49  // FormatCode formats go source codes.
    50  func FormatCode(code []byte) ([]byte, error) {
    51  	formatCode, err := format.Source(code)
    52  	if err != nil {
    53  		return code, fmt.Errorf("format code error: %s", err)
    54  	}
    55  	return formatCode, nil
    56  }
    57  
    58  // GetGOPATH retrieves the GOPATH from environment variables or the `go env` command.
    59  func GetGOPATH() string {
    60  	goPath := os.Getenv("GOPATH")
    61  	// If there are many path in GOPATH, pick up the first one.
    62  	if GoPaths := strings.Split(goPath, ":"); len(GoPaths) >= 1 && strings.TrimSpace(GoPaths[0]) != "" {
    63  		return strings.TrimSpace(GoPaths[0])
    64  	}
    65  	// GOPATH not set through environment variables, try to get one by executing "go env GOPATH"
    66  	output, err := exec.Command("go", "env", "GOPATH").Output()
    67  	if err != nil {
    68  		log.Warn(err)
    69  		os.Exit(1)
    70  	}
    71  
    72  	goPath = strings.TrimSpace(string(output))
    73  	if len(goPath) == 0 {
    74  		buildContext := build.Default
    75  		goPath = buildContext.GOPATH
    76  	}
    77  
    78  	if len(goPath) == 0 {
    79  		panic("GOPATH not found")
    80  	}
    81  	return goPath
    82  }
    83  
    84  // Exists reports whether a file exists.
    85  func Exists(path string) bool {
    86  	fi, err := os.Stat(path)
    87  	if err != nil {
    88  		return os.IsExist(err)
    89  	}
    90  	return !fi.IsDir()
    91  }
    92  
    93  // LowerFirst converts the first letter to upper case for the given string.
    94  func LowerFirst(s string) string {
    95  	rs := []rune(s)
    96  	rs[0] = unicode.ToLower(rs[0])
    97  	return string(rs)
    98  }
    99  
   100  // ReplaceString be used in string substitution.
   101  func ReplaceString(s, old, new string, n int) string {
   102  	return strings.Replace(s, old, new, n)
   103  }
   104  
   105  // SnakeString converts the string 's' to a snake string
   106  func SnakeString(s string) string {
   107  	data := make([]byte, 0, len(s)*2)
   108  	j := false
   109  	for _, d := range []byte(s) {
   110  		if d >= 'A' && d <= 'Z' {
   111  			if j {
   112  				data = append(data, '_')
   113  				j = false
   114  			}
   115  		} else if d != '_' {
   116  			j = true
   117  		}
   118  		data = append(data, d)
   119  	}
   120  	return strings.ToLower(string(data))
   121  }
   122  
   123  // UpperFirst converts the first letter to upper case for the given string.
   124  func UpperFirst(s string) string {
   125  	rs := []rune(s)
   126  	rs[0] = unicode.ToUpper(rs[0])
   127  	return string(rs)
   128  }
   129  
   130  // NotPtr converts an pointer type into non-pointer type.
   131  func NotPtr(s string) string {
   132  	return strings.ReplaceAll(s, "*", "")
   133  }
   134  
   135  // SearchGoMod searches go.mod from the given directory (which must be an absolute path) to
   136  // the root directory. When the go.mod is found, its module name and path will be returned.
   137  func SearchGoMod(cwd string) (moduleName, path string, found bool) {
   138  	for {
   139  		path = filepath.Join(cwd, "go.mod")
   140  		data, err := ioutil.ReadFile(path)
   141  		if err == nil {
   142  			re := regexp.MustCompile(`^\s*module\s+(\S+)\s*`)
   143  			for _, line := range strings.Split(string(data), "\n") {
   144  				m := re.FindStringSubmatch(line)
   145  				if m != nil {
   146  					return m[1], cwd, true
   147  				}
   148  			}
   149  			return fmt.Sprintf("<module name not found in '%s'>", path), path, true
   150  		}
   151  
   152  		if !os.IsNotExist(err) {
   153  			return
   154  		}
   155  		parentCwd := filepath.Dir(cwd)
   156  		if parentCwd == cwd {
   157  			break
   158  		}
   159  		cwd = parentCwd
   160  	}
   161  	return
   162  }
   163  
   164  func RunGitCommand(gitLink string) (string, string, error) {
   165  	u, err := user.Current()
   166  	if err != nil {
   167  		return "", "Failed to get home dir", err
   168  	}
   169  	cachePath := JoinPath(u.HomeDir, ".kitex", "cache")
   170  
   171  	branch := ""
   172  	if strings.Contains(gitLink, ".git@") {
   173  		strs := strings.Split(gitLink, ".git@")
   174  		branch = strs[1]
   175  		gitLink = strs[0] + ".git"
   176  	}
   177  	pullLink := gitLink
   178  
   179  	gitLink = strings.TrimPrefix(gitLink, "git@")
   180  
   181  	gitLink = strings.TrimSuffix(gitLink, ".git")
   182  
   183  	repoLink := ""
   184  	if strings.Contains(gitLink, "://") {
   185  		repoLink = strings.Split(gitLink, "://")[1]
   186  	} else {
   187  		repoLink = strings.ReplaceAll(gitLink, ":", "/")
   188  	}
   189  
   190  	branchSuffix := ""
   191  	if branch != "" {
   192  		branchSuffix = "@" + branch
   193  	}
   194  	gitPath := JoinPath(cachePath, repoLink+branchSuffix)
   195  
   196  	_, err = os.Stat(JoinPath(gitPath, ".git"))
   197  	if err != nil && !os.IsExist(err) {
   198  		err = os.MkdirAll(gitPath, os.ModePerm)
   199  		if err != nil {
   200  			return "", "Failed to create cache directory,please check your permission for ~/.kitex/cache", err
   201  		}
   202  		cmdClone := exec.Command("git", "clone", pullLink, ".")
   203  		cmdClone.Dir = gitPath
   204  		out, gitErr := cmdClone.CombinedOutput()
   205  		if gitErr != nil {
   206  			return "", string(out), gitErr
   207  		}
   208  		if branch != "" {
   209  			cmdCheckout := exec.Command("git", "checkout", branch)
   210  			cmdCheckout.Dir = gitPath
   211  			out, gitErr = cmdCheckout.CombinedOutput()
   212  			return gitPath, string(out), gitErr
   213  		} else {
   214  			return gitPath, "", nil
   215  		}
   216  	}
   217  
   218  	cmdPull := exec.Command("git", "pull")
   219  	cmdPull.Dir = gitPath
   220  	out, gitErr := cmdPull.CombinedOutput()
   221  	if gitErr != nil {
   222  		return "", string(out), gitErr
   223  	}
   224  
   225  	return gitPath, "", nil
   226  }
   227  
   228  // CombineOutputPath read the output and path variables and render them into the final path
   229  func CombineOutputPath(outputPath, ns string) string {
   230  	if ns != "" {
   231  		ns = strings.ReplaceAll(ns, ".", "/")
   232  	}
   233  	hasVarNs := strings.Contains(outputPath, "{namespace}")
   234  	hasVarNsUnderscore := strings.Contains(outputPath, "{namespaceUnderscore}")
   235  	if hasVarNs || hasVarNsUnderscore {
   236  		if hasVarNs {
   237  			outputPath = strings.ReplaceAll(outputPath, "{namespace}", ns)
   238  		} else if hasVarNsUnderscore {
   239  			outputPath = strings.ReplaceAll(outputPath, "{namespaceUnderscore}", strings.ReplaceAll(ns, "/", "_"))
   240  		}
   241  	} else {
   242  		outputPath = JoinPath(outputPath, ns)
   243  	}
   244  	return outputPath
   245  }
   246  
   247  // JoinPath joins dirs as golang import format, such as xx/xx/xx
   248  func JoinPath(elem ...string) string {
   249  	if runtime.GOOS == "windows" {
   250  		return strings.ReplaceAll(filepath.Join(elem...), "\\", "/")
   251  	}
   252  	return filepath.Join(elem...)
   253  }
   254  
   255  // DownloadFile Download file to local
   256  func DownloadFile(remotePath, localPath string) error {
   257  	resp, err := http.Get(remotePath)
   258  	if err != nil {
   259  		return err
   260  	}
   261  	defer resp.Body.Close()
   262  	if resp.StatusCode != http.StatusOK {
   263  		return fmt.Errorf("failed to download file, http status: %s", resp.Status)
   264  	}
   265  
   266  	body, err := ioutil.ReadAll(resp.Body)
   267  	if err != nil {
   268  		return err
   269  	}
   270  	err = ioutil.WriteFile(localPath, body, 0o644)
   271  	if err != nil {
   272  		return err
   273  	}
   274  	return nil
   275  }
   276  
   277  // IDLName returns the name of the IDL file.
   278  func IDLName(filename string) string {
   279  	return filepath.Base(filename)
   280  }