github.com/olivere/camlistore@v0.0.0-20140121221811-1b7ac2da0199/pkg/cmdmain/cmdmain.go (about) 1 /* 2 Copyright 2013 The Camlistore Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package cmdmain contains the shared implementation for camget, 18 // camput, camtool, and other Camlistore command-line tools. 19 package cmdmain 20 21 import ( 22 "flag" 23 "fmt" 24 "io" 25 "log" 26 "os" 27 "path/filepath" 28 "sort" 29 30 "camlistore.org/pkg/buildinfo" 31 ) 32 33 var ( 34 FlagVersion = flag.Bool("version", false, "show version") 35 FlagHelp = flag.Bool("help", false, "print usage") 36 FlagVerbose = flag.Bool("verbose", false, "extra debug logging") 37 ) 38 39 var ( 40 // ExtraFlagRegistration allows to add more flags from 41 // other packages (with AddFlags) when Main starts. 42 ExtraFlagRegistration = func() {} 43 // PreExit is meant to dump additional stats and other 44 // verbiage before Main terminates. 45 PreExit = func() {} 46 // ExitWithFailure determines whether the command exits 47 // with a non-zero exit status. 48 ExitWithFailure bool 49 ) 50 51 var ErrUsage = UsageError("invalid command") 52 53 type UsageError string 54 55 func (ue UsageError) Error() string { 56 return "Usage error: " + string(ue) 57 } 58 59 var ( 60 // mode name to actual subcommand mapping 61 modeCommand = make(map[string]CommandRunner) 62 modeFlags = make(map[string]*flag.FlagSet) 63 64 // Indirections for replacement by tests 65 Stderr io.Writer = os.Stderr 66 Stdout io.Writer = os.Stdout 67 Stdin io.Reader = os.Stdin 68 69 Exit = realExit 70 // TODO: abstract out vfs operation. should never call os.Stat, os.Open, os.Create, etc. 71 // Only use fs.Stat, fs.Open, where vs is an interface type. 72 // TODO: switch from using the global flag FlagSet and use our own. right now 73 // running "go test -v" dumps the flag usage data to the global stderr. 74 ) 75 76 func realExit(code int) { 77 os.Exit(code) 78 } 79 80 // CommandRunner is the type that a command mode should implement. 81 type CommandRunner interface { 82 Usage() 83 RunCommand(args []string) error 84 } 85 86 type exampler interface { 87 Examples() []string 88 } 89 90 type describer interface { 91 Describe() string 92 } 93 94 // RegisterCommand adds a mode to the list of modes for the main command. 95 // It is meant to be called in init() for each subcommand. 96 func RegisterCommand(mode string, makeCmd func(Flags *flag.FlagSet) CommandRunner) { 97 if _, dup := modeCommand[mode]; dup { 98 log.Fatalf("duplicate command %q registered", mode) 99 } 100 flags := flag.NewFlagSet(mode+" options", flag.ContinueOnError) 101 flags.Usage = func() {} 102 modeFlags[mode] = flags 103 modeCommand[mode] = makeCmd(flags) 104 } 105 106 type namedMode struct { 107 Name string 108 Command CommandRunner 109 } 110 111 // TODO(mpl): do we actually need this? I changed usage 112 // to simply iterate over all of modeCommand and it seems 113 // fine. 114 func allModes(startModes []string) <-chan namedMode { 115 ch := make(chan namedMode) 116 go func() { 117 defer close(ch) 118 done := map[string]bool{} 119 for _, name := range startModes { 120 done[name] = true 121 cmd := modeCommand[name] 122 if cmd == nil { 123 panic("bogus mode: " + name) 124 } 125 ch <- namedMode{name, cmd} 126 } 127 var rest []string 128 for name := range modeCommand { 129 if !done[name] { 130 rest = append(rest, name) 131 } 132 } 133 sort.Strings(rest) 134 for _, name := range rest { 135 ch <- namedMode{name, modeCommand[name]} 136 } 137 }() 138 return ch 139 } 140 141 func hasFlags(flags *flag.FlagSet) bool { 142 any := false 143 flags.VisitAll(func(*flag.Flag) { 144 any = true 145 }) 146 return any 147 } 148 149 // Errorf prints to Stderr 150 func Errorf(format string, args ...interface{}) { 151 fmt.Fprintf(Stderr, format, args...) 152 } 153 154 func usage(msg string) { 155 cmdName := filepath.Base(os.Args[0]) 156 if msg != "" { 157 Errorf("Error: %v\n", msg) 158 } 159 Errorf(` 160 Usage: ` + cmdName + ` [globalopts] <mode> [commandopts] [commandargs] 161 162 Modes: 163 164 `) 165 for mode, cmd := range modeCommand { 166 if des, ok := cmd.(describer); ok { 167 Errorf(" %s: %s\n", mode, des.Describe()) 168 } 169 } 170 Errorf("\nExamples:\n") 171 for mode, cmd := range modeCommand { 172 if ex, ok := cmd.(exampler); ok { 173 Errorf("\n") 174 for _, example := range ex.Examples() { 175 Errorf(" %s %s %s\n", cmdName, mode, example) 176 } 177 } 178 } 179 180 Errorf(` 181 For mode-specific help: 182 183 ` + cmdName + ` <mode> -help 184 185 Global options: 186 `) 187 flag.PrintDefaults() 188 Exit(1) 189 } 190 191 func help(mode string) { 192 cmdName := os.Args[0] 193 // We can skip all the checks as they're done in Main 194 cmd := modeCommand[mode] 195 cmdFlags := modeFlags[mode] 196 if des, ok := cmd.(describer); ok { 197 Errorf("%s\n", des.Describe()) 198 } 199 Errorf("\n") 200 cmd.Usage() 201 if hasFlags(cmdFlags) { 202 cmdFlags.PrintDefaults() 203 } 204 if ex, ok := cmd.(exampler); ok { 205 Errorf("\nExamples:\n") 206 for _, example := range ex.Examples() { 207 Errorf(" %s %s %s\n", cmdName, mode, example) 208 } 209 } 210 } 211 212 // Main is meant to be the core of a command that has 213 // subcommands (modes), such as camput or camtool. 214 func Main() { 215 ExtraFlagRegistration() 216 flag.Parse() 217 args := flag.Args() 218 if *FlagVersion { 219 fmt.Fprintf(Stderr, "%s version: %s\n", os.Args[0], buildinfo.Version()) 220 return 221 } 222 if *FlagHelp { 223 usage("") 224 } 225 if len(args) == 0 { 226 usage("No mode given.") 227 } 228 229 mode := args[0] 230 cmd, ok := modeCommand[mode] 231 if !ok { 232 usage(fmt.Sprintf("Unknown mode %q", mode)) 233 } 234 235 cmdFlags := modeFlags[mode] 236 var cmdHelp bool 237 cmdFlags.BoolVar(&cmdHelp, "help", false, "Help for this mode.") 238 err := cmdFlags.Parse(args[1:]) 239 if err != nil { 240 err = ErrUsage 241 } else { 242 if cmdHelp { 243 help(mode) 244 return 245 } 246 err = cmd.RunCommand(cmdFlags.Args()) 247 } 248 if ue, isUsage := err.(UsageError); isUsage { 249 if isUsage { 250 Errorf("%s\n", ue) 251 } 252 cmd.Usage() 253 Errorf("\nGlobal options:\n") 254 flag.PrintDefaults() 255 256 if hasFlags(cmdFlags) { 257 Errorf("\nMode-specific options for mode %q:\n", mode) 258 cmdFlags.PrintDefaults() 259 } 260 Exit(1) 261 } 262 if *FlagVerbose { 263 PreExit() 264 } 265 if err != nil { 266 if !ExitWithFailure { 267 // because it was already logged if ExitWithFailure 268 log.Printf("Error: %v", err) 269 } 270 Exit(2) 271 } 272 }