github.com/ianfoo/lab@v0.9.5-0.20180123060006-5ed79f2ccfc7/cmd/root.go (about) 1 package cmd 2 3 import ( 4 "bytes" 5 "fmt" 6 "log" 7 "os" 8 "os/exec" 9 "strconv" 10 "strings" 11 "syscall" 12 "text/template" 13 "unicode" 14 15 "github.com/pkg/errors" 16 "github.com/spf13/cobra" 17 gitconfig "github.com/tcnksm/go-gitconfig" 18 "github.com/zaquestion/lab/internal/git" 19 lab "github.com/zaquestion/lab/internal/gitlab" 20 ) 21 22 // RootCmd represents the base command when called without any subcommands 23 var RootCmd = &cobra.Command{ 24 Use: "lab", 25 Short: "A Git Wrapper for GitLab", 26 Long: ``, 27 Run: func(cmd *cobra.Command, args []string) { 28 formatChar := "\n" 29 if git.IsHub { 30 formatChar = "" 31 } 32 33 git := git.New() 34 git.Stdout = nil 35 git.Stderr = nil 36 usage, _ := git.CombinedOutput() 37 fmt.Printf("%s%sThese GitLab commands are provided by lab:\n%s\n\n", string(usage), formatChar, labUsage(cmd)) 38 }, 39 } 40 41 func trimRightSpace(s string) string { 42 return strings.TrimRightFunc(s, unicode.IsSpace) 43 } 44 45 func rpad(s string, padding int) string { 46 template := fmt.Sprintf("%%-%ds", padding) 47 return fmt.Sprintf(template, s) 48 } 49 50 var templateFuncs = template.FuncMap{ 51 "trimTrailingWhitespaces": trimRightSpace, 52 "rpad": rpad, 53 } 54 55 const labUsageTmpl = `{{range .Commands}}{{if (and (or .IsAvailableCommand (ne .Name "help")) (and (ne .Name "clone") (ne .Name "version") (ne .Name "ci")))}} 56 {{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}` 57 58 func labUsage(c *cobra.Command) string { 59 t := template.New("top") 60 t.Funcs(templateFuncs) 61 template.Must(t.Parse(labUsageTmpl)) 62 63 var buf bytes.Buffer 64 err := t.Execute(&buf, c) 65 if err != nil { 66 c.Println(err) 67 } 68 return buf.String() 69 } 70 71 // parseArgsRemote returns the remote and a number if parsed. Many commands 72 // accept a remote to operate on and number such as a page id 73 func parseArgsRemote(args []string) (string, int64, error) { 74 if len(args) == 2 { 75 n, err := strconv.ParseInt(args[1], 0, 64) 76 if err != nil { 77 return "", 0, err 78 } 79 ok, err := git.IsRemote(args[0]) 80 if err != nil { 81 return "", 0, err 82 } else if !ok { 83 return "", 0, errors.Errorf("%s is not a valid remote", args[0]) 84 } 85 return args[0], n, nil 86 } 87 if len(args) == 1 { 88 ok, err := git.IsRemote(args[0]) 89 if err != nil { 90 return "", 0, err 91 } 92 if ok { 93 return args[0], 0, nil 94 } 95 n, err := strconv.ParseInt(args[0], 0, 64) 96 if err == nil { 97 return "", n, nil 98 } 99 return "", 0, errors.Errorf("%s is not a valid remote or number", args[0]) 100 } 101 return "", 0, nil 102 } 103 104 func parseArgs(args []string) (string, int64, error) { 105 remote, num, err := parseArgsRemote(args) 106 if err != nil { 107 return "", 0, err 108 } 109 if remote == "" { 110 remote = forkedFromRemote 111 } 112 rn, err := git.PathWithNameSpace(remote) 113 if err != nil { 114 return "", 0, err 115 } 116 return rn, num, nil 117 } 118 119 var ( 120 // Will be updated to upstream in init() if "upstream" remote exists 121 forkedFromRemote = "origin" 122 // Will be updated to lab.User() in init() if forkedFrom is "origin" 123 forkRemote = "origin" 124 ) 125 126 // Execute adds all child commands to the root command and sets flags appropriately. 127 // This is called by main.main(). It only needs to happen once to the rootCmd. 128 func Execute() { 129 _, err := gitconfig.Local("remote.upstream.url") 130 if err == nil { 131 forkedFromRemote = "upstream" 132 } 133 134 if forkedFromRemote == "origin" { 135 // Check if the user fork exists 136 _, err = gitconfig.Local("remote." + lab.User() + ".url") 137 if err == nil { 138 forkRemote = lab.User() 139 } 140 } 141 if cmd, _, err := RootCmd.Find(os.Args[1:]); err != nil || cmd.Use == "clone" { 142 // Determine if any undefined flags were passed to "clone" 143 if cmd.Use == "clone" && len(os.Args) > 2 { 144 // ParseFlags will err in these cases 145 err = cmd.ParseFlags(os.Args[1:]) 146 if err == nil { 147 if err := RootCmd.Execute(); err != nil { 148 // Execute has already logged the error 149 os.Exit(1) 150 } 151 return 152 } 153 } 154 155 // Passthrough to git for any unrecognised commands 156 git := git.New(os.Args[1:]...) 157 err = git.Run() 158 if exiterr, ok := err.(*exec.ExitError); ok { 159 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 160 os.Exit(status.ExitStatus()) 161 } 162 } 163 if err != nil { 164 log.Fatal(err) 165 } 166 return 167 } 168 if err := RootCmd.Execute(); err != nil { 169 // Execute has already logged the error 170 os.Exit(1) 171 } 172 }