github.com/tcnksm/gcli@v0.2.4-0.20170129033839-7eb950507e5a/command/apply.go (about)

     1  package command
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"strings"
     8  
     9  	"github.com/BurntSushi/toml"
    10  	"github.com/tcnksm/gcli/skeleton"
    11  )
    12  
    13  // ApplyCommand is a Command that generates a new cli project
    14  type ApplyCommand struct {
    15  	Meta
    16  }
    17  
    18  // Run generates a new cli project. It returns exit code
    19  func (c *ApplyCommand) Run(args []string) int {
    20  
    21  	var (
    22  		frameworkStr string
    23  		owner        string
    24  		name         string
    25  		staticDir    string
    26  		vcsHost      string
    27  		current      bool
    28  		skipTest     bool
    29  		verbose      bool
    30  	)
    31  
    32  	uflag := c.Meta.NewFlagSet("apply", c.Help())
    33  
    34  	uflag.StringVar(&frameworkStr, "framework", "", "framework")
    35  	uflag.StringVar(&frameworkStr, "F", "", "framework (short)")
    36  
    37  	uflag.StringVar(&staticDir, "static-dir", "", "")
    38  
    39  	uflag.StringVar(&vcsHost, "vcs", DefaultVCSHost, "")
    40  
    41  	uflag.BoolVar(&current, "current", false, "current")
    42  	uflag.BoolVar(&current, "C", false, "current")
    43  
    44  	uflag.BoolVar(&skipTest, "skip-test", false, "skip-test")
    45  	uflag.BoolVar(&skipTest, "T", false, "skip-test (short)")
    46  
    47  	uflag.BoolVar(&verbose, "verbose", false, "verbose")
    48  	uflag.BoolVar(&verbose, "V", false, "verbose (short)")
    49  
    50  	// These flags are supposed only to use in test
    51  	uflag.StringVar(&owner, "owner", "", "owner (Should only for test)")
    52  	uflag.StringVar(&name, "name", "", "name (Should only for test)")
    53  
    54  	if err := uflag.Parse(args); err != nil {
    55  		return 1
    56  	}
    57  
    58  	parsedArgs := uflag.Args()
    59  	if len(parsedArgs) != 1 {
    60  		c.UI.Error("Invalid argument: Usage glic apply [options] FILE")
    61  		return 1
    62  	}
    63  
    64  	designFile := parsedArgs[0]
    65  	c.UI.Info(fmt.Sprintf(
    66  		"Use design template %q for generating new cli project", designFile))
    67  
    68  	// Check file is exist or not
    69  	if _, err := os.Stat(designFile); os.IsNotExist(err) {
    70  		c.UI.Error(fmt.Sprintf(
    71  			"Design file does not exsit"))
    72  		return 1
    73  	}
    74  
    75  	// Decode design file as skeleton.Executable
    76  	executable := skeleton.NewExecutable()
    77  	if _, err := toml.DecodeFile(designFile, executable); err != nil {
    78  		c.UI.Error(fmt.Sprintf(
    79  			"Failed to decode design file %q: %s", designFile, err))
    80  		return 1
    81  	}
    82  
    83  	if err := executable.Fix(); err != nil {
    84  		c.UI.Error(fmt.Sprintf(
    85  			"Failed to fix input value: %s", err))
    86  		return 1
    87  	}
    88  
    89  	// validate executable
    90  	if errs := executable.Validate(); len(errs) > 0 {
    91  		c.UI.Error(fmt.Sprintf(
    92  			"%q is not valid template file. It has %d errors:", designFile, len(errs)))
    93  		for _, err := range errs {
    94  			c.UI.Error(fmt.Sprintf(
    95  				"  * %s", err.Error()))
    96  		}
    97  		return ExitCodeFailed
    98  	}
    99  
   100  	// Check option input first and if it's specified use it
   101  	if len(frameworkStr) == 0 {
   102  		if len(executable.FrameworkStr) != 0 {
   103  			// If FrameworStr is specified from design file use it
   104  			frameworkStr = executable.FrameworkStr
   105  		} else {
   106  			frameworkStr = defaultFrameworkString
   107  		}
   108  	}
   109  
   110  	// Overwrite VCSHost
   111  	executable.VCSHost = vcsHost
   112  
   113  	// Overwrite output and executable name
   114  	output := executable.Name
   115  	if len(name) != 0 {
   116  		executable.Name = name
   117  		output = name
   118  	}
   119  
   120  	// Overwrite owner name
   121  	if len(owner) != 0 {
   122  		executable.Owner = owner
   123  	}
   124  
   125  	currentDir, err := os.Getwd()
   126  	if err != nil {
   127  		c.UI.Error(fmt.Sprintf(
   128  			"Failed to get current directroy: %s", err))
   129  		return ExitCodeFailed
   130  	}
   131  
   132  	gopath := os.Getenv(EnvGoPath)
   133  	if gopath == "" {
   134  		c.UI.Error(fmt.Sprintf(
   135  			"Failed to read GOPATH: it should not be empty"))
   136  		return ExitCodeFailed
   137  	}
   138  	idealDir := filepath.Join(gopath, "src", executable.VCSHost, executable.Owner)
   139  
   140  	if currentDir != idealDir && !current {
   141  		c.UI.Output("")
   142  		c.UI.Output(fmt.Sprintf("====> WARNING: You are not in the directory gcli expects."))
   143  		c.UI.Output(fmt.Sprintf("      The codes will be generated be in $GOPATH/src/%s/%s.",
   144  			executable.VCSHost, executable.Owner))
   145  		c.UI.Output(fmt.Sprintf("      Not in the current directory. This is because the output"))
   146  		c.UI.Output(fmt.Sprintf("      codes use import path based on that path."))
   147  		c.UI.Output("")
   148  		output = filepath.Join(idealDir, executable.Name)
   149  	}
   150  
   151  	if _, err := os.Stat(output); !os.IsNotExist(err) {
   152  		msg := fmt.Sprintf("Cannot create directory %s: file exists", output)
   153  		c.UI.Error(msg)
   154  		return 1
   155  	}
   156  
   157  	if staticDir == "" {
   158  		localDir, err := c.LocalDir()
   159  		if err != nil {
   160  			c.UI.Error(err.Error())
   161  			return ExitCodeFailed
   162  		}
   163  		staticDir = filepath.Join(localDir, DefaultLocalStaticDir)
   164  	}
   165  
   166  	framework, err := skeleton.FrameworkByName(frameworkStr)
   167  	if err != nil {
   168  		c.UI.Error(fmt.Sprintf("Failed to generate %q: %s", executable.Name, err.Error()))
   169  		return 1
   170  	}
   171  
   172  	// Channels to receive artifact path (result) and error
   173  	artifactCh, errCh := make(chan string), make(chan error)
   174  
   175  	// Define Skeleton
   176  	skeleton := &skeleton.Skeleton{
   177  		Path:       output,
   178  		StaticDir:  staticDir,
   179  		Framework:  framework,
   180  		SkipTest:   skipTest,
   181  		Executable: executable,
   182  		ArtifactCh: artifactCh,
   183  		ErrCh:      errCh,
   184  		Verbose:    verbose,
   185  		LogWriter:  os.Stdout,
   186  	}
   187  
   188  	// Create project directory
   189  	doneCh := skeleton.Generate()
   190  
   191  	for {
   192  		select {
   193  		case artifact := <-artifactCh:
   194  			c.UI.Output(fmt.Sprintf("  Created %s", artifact))
   195  		case err := <-errCh:
   196  			c.UI.Error(fmt.Sprintf("Failed to generate %s: %s", output, err.Error()))
   197  
   198  			// If some file are created before error happend
   199  			// Should be cleanuped
   200  			if _, err := os.Stat(output); !os.IsNotExist(err) {
   201  				c.UI.Output(fmt.Sprintf("Cleanup %s", output))
   202  				os.RemoveAll(output)
   203  			}
   204  			return ExitCodeFailed
   205  		case <-doneCh:
   206  			c.UI.Info(fmt.Sprintf("====> Successfully generated %s", executable.Name))
   207  			return ExitCodeOK
   208  		}
   209  	}
   210  }
   211  
   212  // Synopsis is a one-line, short synopsis of the command.
   213  func (c *ApplyCommand) Synopsis() string {
   214  	return "Apply design template file for generating cli project"
   215  }
   216  
   217  // Help is a long-form help text that includes the command-line
   218  // usage, a brief few sentences explaining the function of the command,
   219  // and the complete list of flags the command accepts.
   220  func (c *ApplyCommand) Help() string {
   221  	helpText := `
   222  Apply design template file for generating cli project. You can generate
   223  design template file via 'gcli design' command. If framework name is not
   224  specified gcli use codegangsta/cli. You can set framework name via '-F'
   225  option. To check cli framework you can use, run 'gcli list'. 
   226  
   227  Usage:
   228  
   229    gcli apply [option] FILE
   230  
   231  
   232  Options:
   233  
   234     -framework=name, -F        Cli framework name. By default, gcli use "codegangsta/cli"
   235                                To check cli framework you can use, run 'gcli list'.
   236                                If you set invalid framework, it will be failed.
   237  
   238     -vcs=name                  Version Control Host name. By default, gcli use 'github.com'.
   239  
   240     -skip-test, -T             Skip generating *_test.go file. By default, gcli generates
   241                                test file If you specify this flag, gcli will not generate
   242                                test files.
   243  `
   244  	return strings.TrimSpace(helpText)
   245  }