github.com/chaddy81/ponzu@v0.0.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 }