go-micro.dev/v5@v5.12.0/cmd/micro/cli/new/new.go (about)

     1  // Package new generates micro service templates
     2  package new
     3  
     4  import (
     5  	"fmt"
     6  	"go/build"
     7  	"os"
     8  	"os/exec"
     9  	"path"
    10  	"path/filepath"
    11  	"runtime"
    12  	"strings"
    13  	"text/template"
    14  	"time"
    15  
    16  	"github.com/urfave/cli/v2"
    17  	"github.com/xlab/treeprint"
    18  	tmpl "go-micro.dev/v5/cmd/micro/cli/new/template"
    19  )
    20  
    21  func protoComments(goDir, alias string) []string {
    22  	return []string{
    23  		"\ndownload protoc zip packages (protoc-$VERSION-$PLATFORM.zip) and install:\n",
    24  		"visit https://github.com/protocolbuffers/protobuf/releases",
    25  		"\ncompile the proto file " + alias + ".proto:\n",
    26  		"cd " + alias,
    27  		"go mod tidy",
    28  		"make proto\n",
    29  	}
    30  }
    31  
    32  type config struct {
    33  	// foo
    34  	Alias string
    35  	// github.com/micro/foo
    36  	Dir string
    37  	// $GOPATH/src/github.com/micro/foo
    38  	GoDir string
    39  	// $GOPATH
    40  	GoPath string
    41  	// UseGoPath
    42  	UseGoPath bool
    43  	// Files
    44  	Files []file
    45  	// Comments
    46  	Comments []string
    47  }
    48  
    49  type file struct {
    50  	Path string
    51  	Tmpl string
    52  }
    53  
    54  func write(c config, file, tmpl string) error {
    55  	fn := template.FuncMap{
    56  		"title": func(s string) string {
    57  			return strings.ReplaceAll(strings.Title(s), "-", "")
    58  		},
    59  		"dehyphen": func(s string) string {
    60  			return strings.ReplaceAll(s, "-", "")
    61  		},
    62  		"lower": func(s string) string {
    63  			return strings.ToLower(s)
    64  		},
    65  	}
    66  
    67  	f, err := os.Create(file)
    68  	if err != nil {
    69  		return err
    70  	}
    71  	defer f.Close()
    72  
    73  	t, err := template.New("f").Funcs(fn).Parse(tmpl)
    74  	if err != nil {
    75  		return err
    76  	}
    77  
    78  	return t.Execute(f, c)
    79  }
    80  
    81  func create(c config) error {
    82  	// check if dir exists
    83  	if _, err := os.Stat(c.Dir); !os.IsNotExist(err) {
    84  		return fmt.Errorf("%s already exists", c.Dir)
    85  	}
    86  
    87  	fmt.Printf("Creating service %s\n\n", c.Alias)
    88  
    89  	t := treeprint.New()
    90  
    91  	// write the files
    92  	for _, file := range c.Files {
    93  		f := filepath.Join(c.Dir, file.Path)
    94  		dir := filepath.Dir(f)
    95  
    96  		if _, err := os.Stat(dir); os.IsNotExist(err) {
    97  			if err := os.MkdirAll(dir, 0755); err != nil {
    98  				return err
    99  			}
   100  		}
   101  
   102  		addFileToTree(t, file.Path)
   103  		if err := write(c, f, file.Tmpl); err != nil {
   104  			return err
   105  		}
   106  	}
   107  
   108  	// print tree
   109  	fmt.Println(t.String())
   110  
   111  	for _, comment := range c.Comments {
   112  		fmt.Println(comment)
   113  	}
   114  
   115  	// just wait
   116  	<-time.After(time.Millisecond * 250)
   117  
   118  	return nil
   119  }
   120  
   121  func addFileToTree(root treeprint.Tree, file string) {
   122  	split := strings.Split(file, "/")
   123  	curr := root
   124  	for i := 0; i < len(split)-1; i++ {
   125  		n := curr.FindByValue(split[i])
   126  		if n != nil {
   127  			curr = n
   128  		} else {
   129  			curr = curr.AddBranch(split[i])
   130  		}
   131  	}
   132  	if curr.FindByValue(split[len(split)-1]) == nil {
   133  		curr.AddNode(split[len(split)-1])
   134  	}
   135  }
   136  
   137  func Run(ctx *cli.Context) error {
   138  	dir := ctx.Args().First()
   139  	if len(dir) == 0 {
   140  		fmt.Println("specify service name")
   141  		return nil
   142  	}
   143  
   144  	// check if the path is absolute, we don't want this
   145  	// we want to a relative path so we can install in GOPATH
   146  	if path.IsAbs(dir) {
   147  		fmt.Println("require relative path as service will be installed in GOPATH")
   148  		return nil
   149  	}
   150  
   151  	// Check for protoc
   152  	if _, err := exec.LookPath("protoc"); err != nil {
   153  		fmt.Println("WARNING: protoc is not installed or not in your PATH.")
   154  		fmt.Println("Please install protoc from https://github.com/protocolbuffers/protobuf/releases")
   155  		fmt.Println("After installing, re-run 'make proto' in your service directory if needed.")
   156  	}
   157  
   158  	var goPath string
   159  	var goDir string
   160  
   161  	goPath = build.Default.GOPATH
   162  
   163  	// don't know GOPATH, runaway....
   164  	if len(goPath) == 0 {
   165  		fmt.Println("unknown GOPATH")
   166  		return nil
   167  	}
   168  
   169  	// attempt to split path if not windows
   170  	if runtime.GOOS == "windows" {
   171  		goPath = strings.Split(goPath, ";")[0]
   172  	} else {
   173  		goPath = strings.Split(goPath, ":")[0]
   174  	}
   175  	goDir = filepath.Join(goPath, "src", path.Clean(dir))
   176  
   177  	c := config{
   178  		Alias:     dir,
   179  		Comments:  nil, // Remove redundant protoComments
   180  		Dir:       dir,
   181  		GoDir:     goDir,
   182  		GoPath:    goPath,
   183  		UseGoPath: false,
   184  		Files: []file{
   185  			{"main.go", tmpl.MainSRV},
   186  			{"handler/" + dir + ".go", tmpl.HandlerSRV},
   187  			{"proto/" + dir + ".proto", tmpl.ProtoSRV},
   188  			{"Makefile", tmpl.Makefile},
   189  			{"README.md", tmpl.Readme},
   190  			{".gitignore", tmpl.GitIgnore},
   191  		},
   192  	}
   193  
   194  	// set gomodule
   195  	if os.Getenv("GO111MODULE") != "off" {
   196  		c.Files = append(c.Files, file{"go.mod", tmpl.Module})
   197  	}
   198  
   199  	// create the files
   200  	if err := create(c); err != nil {
   201  		return err
   202  	}
   203  
   204  	// Run go mod tidy and make proto
   205  	fmt.Println("\nRunning 'go mod tidy' and 'make proto'...")
   206  	if err := runInDir(dir, "go mod tidy"); err != nil {
   207  		fmt.Printf("Error running 'go mod tidy': %v\n", err)
   208  	}
   209  	if err := runInDir(dir, "make proto"); err != nil {
   210  		fmt.Printf("Error running 'make proto': %v\n", err)
   211  	}
   212  
   213  	// Print updated tree including generated files
   214  	fmt.Println("\nProject structure after 'make proto':")
   215  	printTree(dir)
   216  
   217  	fmt.Println("\nService created successfully! Start coding in your new service directory.")
   218  	return nil
   219  }
   220  
   221  func runInDir(dir, cmd string) error {
   222  	parts := strings.Fields(cmd)
   223  	c := exec.Command(parts[0], parts[1:]...)
   224  	c.Dir = dir
   225  	c.Stdout = os.Stdout
   226  	c.Stderr = os.Stderr
   227  	return c.Run()
   228  }
   229  
   230  func printTree(dir string) {
   231  	t := treeprint.New()
   232  	walk := func(path string, info os.FileInfo, err error) error {
   233  		if err != nil {
   234  			return err
   235  		}
   236  		rel, _ := filepath.Rel(dir, path)
   237  		if rel == "." {
   238  			return nil
   239  		}
   240  		parts := strings.Split(rel, string(os.PathSeparator))
   241  		curr := t
   242  		for i := 0; i < len(parts)-1; i++ {
   243  			n := curr.FindByValue(parts[i])
   244  			if n != nil {
   245  				curr = n
   246  			} else {
   247  				curr = curr.AddBranch(parts[i])
   248  			}
   249  		}
   250  		if !info.IsDir() {
   251  			curr.AddNode(parts[len(parts)-1])
   252  		}
   253  		return nil
   254  	}
   255  	filepath.Walk(dir, walk)
   256  	fmt.Println(t.String())
   257  }