github.com/bosssauce/ponzu@v0.11.1-0.20200102001432-9bc41b703131/cmd/ponzu/add.go (about)

     1  package main
     2  
     3  import (
     4  	"errors"
     5  	"fmt"
     6  	"io"
     7  	"os"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/spf13/cobra"
    12  )
    13  
    14  var addCmd = &cobra.Command{
    15  	Use:     "add <import path>",
    16  	Aliases: []string{"a"},
    17  	Short:   "Downloads addon from specified import path",
    18  	Long: `Downloads addon from specified import path to $GOPATH/src and copys it to the
    19  current project's addons directory. Must be called from within a Ponzu project directory. Addon
    20  needs to imported in at least one content item for it to be included in the Ponzu server build.`,
    21  	Example: `$ ponzu add github.com/bosssauce/fbscheduler`,
    22  	RunE: func(cmd *cobra.Command, args []string) error {
    23  		// expecting two args, add/a and the go gettable package uri
    24  		if len(args) < 1 {
    25  			return errors.New("no import path provided")
    26  		}
    27  
    28  		return getAddon(args[0])
    29  	},
    30  }
    31  
    32  // use `go get` to download addon and add to $GOPATH/src, useful
    33  // for IDE auto-import and code completion, then copy entire directory
    34  // tree to project's ./addons folder
    35  func getAddon(addonPath string) error {
    36  
    37  	var cmdOptions []string
    38  
    39  	// Go get
    40  	cmdOptions = append(cmdOptions, "get", addonPath)
    41  	err := execAndWait(gocmd, cmdOptions...)
    42  	if err != nil {
    43  		return addError(err)
    44  	}
    45  
    46  	// copy to ./addons folder
    47  	// resolve GOPATH
    48  	gopath, err := getGOPATH()
    49  	if err != nil {
    50  		return addError(err)
    51  	}
    52  
    53  	pwd, err := os.Getwd()
    54  	if err != nil {
    55  		return addError(err)
    56  	}
    57  
    58  	src := filepath.Join(gopath, "src", addonPath)
    59  
    60  	// Need to strip the addon name for copyAll?
    61  	last := filepath.Base(addonPath)
    62  	dest := filepath.Join(pwd, "addons", strings.Replace(addonPath, last, "", 1))
    63  
    64  	err = replicateAll(src, dest)
    65  	if err != nil {
    66  		return addError(err)
    67  	}
    68  	return nil
    69  }
    70  
    71  // this is distinct from copyAll() in that files are copied, not moved,
    72  // since we also need them to remain in $GOPATH/src
    73  // thanks to @markc of stack overflow for the copyFile and copyFileContents functions
    74  func replicateAll(src, dst string) error {
    75  	err := filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
    76  		if err != nil {
    77  			return err
    78  		}
    79  
    80  		sep := string(filepath.Separator)
    81  
    82  		// base == the ponzu project dir + string(filepath.Separator)
    83  		parts := strings.Split(src, sep)
    84  		base := strings.Join(parts[:len(parts)-1], sep)
    85  		base += sep
    86  
    87  		target := filepath.Join(dst, path[len(base):])
    88  
    89  		// if its a directory, make dir in dst
    90  		if info.IsDir() {
    91  			err := os.MkdirAll(target, os.ModeDir|os.ModePerm)
    92  			if err != nil {
    93  				return err
    94  			}
    95  		} else {
    96  			// if its a file, copy file to dir of dst
    97  			err = copyFile(path, target)
    98  			if err != nil {
    99  				return err
   100  			}
   101  		}
   102  
   103  		return nil
   104  	})
   105  	if err != nil {
   106  		return err
   107  	}
   108  
   109  	return nil
   110  }
   111  
   112  // copyFile copies a file from src to dst. if src and dst files exist, and are
   113  // the same, then return success. Otherise, attempt to create a hard link
   114  // between the two files. If that fail, copy the file contents from src to dst.
   115  // thanks to Stack Overflow
   116  func copyFile(src, dst string) (err error) {
   117  	sfi, err := os.Stat(src)
   118  	if err != nil {
   119  		return
   120  	}
   121  	if !sfi.Mode().IsRegular() {
   122  		// cannot copy non-regular files (e.g., directories,
   123  		// symlinks, devices, etc.)
   124  		return fmt.Errorf("CopyFile: non-regular source file %s (%q)", sfi.Name(), sfi.Mode().String())
   125  	}
   126  	dfi, err := os.Stat(dst)
   127  	if err != nil {
   128  		if !os.IsNotExist(err) {
   129  			return
   130  		}
   131  	} else {
   132  		if !(dfi.Mode().IsRegular()) {
   133  			return fmt.Errorf("CopyFile: non-regular destination file %s (%q)", dfi.Name(), dfi.Mode().String())
   134  		}
   135  		if os.SameFile(sfi, dfi) {
   136  			return
   137  		}
   138  	}
   139  	if err = os.Link(src, dst); err == nil {
   140  		return
   141  	}
   142  	err = copyFileContents(src, dst)
   143  	return
   144  }
   145  
   146  // copyFileContents copies the contents of the file named src to the file named
   147  // by dst. The file will be created if it does not already exist. If the
   148  // destination file exists, all it's contents will be replaced by the contents
   149  // of the source file.
   150  // Thanks for Stack Overflow
   151  func copyFileContents(src, dst string) (err error) {
   152  	in, err := os.Open(src)
   153  	if err != nil {
   154  		return
   155  	}
   156  	defer in.Close()
   157  	out, err := os.Create(dst)
   158  	if err != nil {
   159  		return
   160  	}
   161  	defer func() {
   162  		cerr := out.Close()
   163  		if err == nil {
   164  			err = cerr
   165  		}
   166  	}()
   167  	if _, err = io.Copy(out, in); err != nil {
   168  		return
   169  	}
   170  	err = out.Sync()
   171  	return
   172  }
   173  
   174  // generic error return
   175  func addError(err error) error {
   176  	return errors.New("Ponzu add failed. " + "\n" + err.Error())
   177  }
   178  
   179  func init() {
   180  	RegisterCmdlineCommand(addCmd)
   181  }