github.com/kcmvp/gob@v1.0.17/cmd/gbc/command/deps.go (about)

     1  package command
     2  
     3  import (
     4  	"bufio"
     5  	"fmt"
     6  	"os"
     7  	"os/exec"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/kcmvp/gob/cmd/gbc/artifact" //nolint
    12  
    13  	"github.com/fatih/color"
    14  	"github.com/samber/lo"
    15  	"github.com/spf13/cobra"
    16  	"github.com/xlab/treeprint"
    17  )
    18  
    19  var (
    20  	green  = color.New(color.FgGreen)
    21  	yellow = color.New(color.FgYellow)
    22  )
    23  
    24  // parseMod return a tuple which the fourth element is the indicator of direct or indirect reference
    25  func parseMod(mod *os.File) (string, string, []*lo.Tuple4[string, string, string, int], error) {
    26  	scanner := bufio.NewScanner(mod)
    27  	var deps []*lo.Tuple4[string, string, string, int]
    28  	var module, version string
    29  	for scanner.Scan() {
    30  		line := strings.TrimSpace(scanner.Text())
    31  		if len(line) == 0 || line == ")" || line == "//" || strings.HasPrefix(line, "require") {
    32  			continue
    33  		}
    34  		if strings.HasPrefix(line, "module ") {
    35  			module = strings.Split(line, " ")[1]
    36  		} else if strings.HasPrefix(line, "go ") {
    37  			version = strings.Split(line, " ")[1]
    38  		} else {
    39  			entry := strings.Split(line, " ")
    40  			m := strings.TrimSpace(entry[0])
    41  			v := strings.TrimSpace(entry[1])
    42  			dep := lo.T4(m, v, v, lo.If(len(entry) > 2, 0).Else(1))
    43  			deps = append(deps, &dep)
    44  		}
    45  	}
    46  	return module, version, deps, scanner.Err()
    47  }
    48  
    49  // dependencyTree build dependency tree of the project, an empty tree returns when runs into error
    50  func dependencyTree() (treeprint.Tree, error) {
    51  	mod, err := os.Open(filepath.Join(artifact.CurProject().Root(), "go.mod"))
    52  	if err != nil {
    53  		return nil, fmt.Errorf(color.RedString(err.Error()))
    54  	}
    55  	exec.Command("go", "mod", "tidy").CombinedOutput() //nolint
    56  	if output, err := exec.Command("go", "build", "./...").CombinedOutput(); err != nil {
    57  		return nil, fmt.Errorf(color.RedString(string(output)))
    58  	}
    59  	module, _, dependencies, err := parseMod(mod)
    60  	if len(dependencies) < 1 {
    61  		return nil, nil
    62  	}
    63  	if err != nil {
    64  		return nil, fmt.Errorf(err.Error())
    65  	}
    66  	tree := treeprint.New()
    67  	tree.SetValue(module)
    68  	direct := lo.FilterMap(dependencies, func(item *lo.Tuple4[string, string, string, int], _ int) (string, bool) {
    69  		return item.A, item.D == 1
    70  	})
    71  	// get the latest version
    72  	versions := artifact.LatestVersion(direct...)
    73  	for _, dep := range dependencies {
    74  		if version, ok := lo.Find(versions, func(t lo.Tuple2[string, string]) bool {
    75  			return dep.A == t.A && dep.B != t.B
    76  		}); ok {
    77  			dep.C = version.B
    78  		}
    79  	}
    80  	// parse the dependency tree
    81  	cache := []string{os.Getenv("GOPATH"), "pkg", "mod", "cache", "download"}
    82  	for _, dependency := range dependencies {
    83  		if dependency.D == 1 {
    84  			label := lo.IfF(dependency.B == dependency.C, func() string {
    85  				return fmt.Sprintf("%s@%s", dependency.A, dependency.B)
    86  			}).ElseF(func() string {
    87  				return yellow.Sprintf("* %s@%s (%s)", dependency.A, dependency.B, dependency.C)
    88  			})
    89  			child := tree.AddBranch(label)
    90  			dir := append(cache, strings.Split(dependency.A, "/")...)
    91  			dir = append(dir, []string{"@v", fmt.Sprintf("%s.mod", dependency.B)}...)
    92  			mod, err = os.Open(filepath.Join(dir...))
    93  			if err != nil {
    94  				return tree, fmt.Errorf(color.RedString(err.Error()))
    95  			}
    96  			_, _, cDeps, err := parseMod(mod)
    97  			if err != nil {
    98  				return tree, fmt.Errorf(color.RedString(err.Error()))
    99  			}
   100  			inter := lo.Filter(cDeps, func(c *lo.Tuple4[string, string, string, int], _ int) bool {
   101  				return lo.ContainsBy(dependencies, func(p *lo.Tuple4[string, string, string, int]) bool {
   102  					return p.A == c.A
   103  				})
   104  			})
   105  			for _, l := range inter {
   106  				child.AddNode(fmt.Sprintf("%s@%s", l.A, l.B))
   107  			}
   108  		}
   109  	}
   110  	return tree, err
   111  }
   112  
   113  // depCmd represents the dep command
   114  var depCmd = &cobra.Command{
   115  	Use:   "deps",
   116  	Short: "Show the dependency tree of the project",
   117  	Long: `Show the dependency tree of the project
   118  and indicate available updates which take an green * indicator`,
   119  	RunE: func(cmd *cobra.Command, args []string) error {
   120  		tree, err := dependencyTree()
   121  		if err != nil {
   122  			return err
   123  		} else if tree == nil {
   124  			yellow.Println("No dependencies !")
   125  			return nil
   126  		}
   127  		green.Println("Dependencies of the projects:")
   128  		fmt.Println(tree.String())
   129  		return nil
   130  	},
   131  }
   132  
   133  func init() {
   134  	depCmd.SetUsageTemplate(usageTemplate())
   135  	depCmd.SetErrPrefix(color.RedString("Error:"))
   136  	rootCmd.AddCommand(depCmd)
   137  }