github.com/slspeek/camlistore_namedsearch@v0.0.0-20140519202248-ed6f70f7721a/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 "sync" 30 31 "camlistore.org/pkg/buildinfo" 32 ) 33 34 var ( 35 FlagVersion = flag.Bool("version", false, "show version") 36 FlagHelp = flag.Bool("help", false, "print usage") 37 FlagVerbose = flag.Bool("verbose", false, "extra debug logging") 38 ) 39 40 var ( 41 // ExtraFlagRegistration allows to add more flags from 42 // other packages (with AddFlags) when Main starts. 43 ExtraFlagRegistration = func() {} 44 // PreExit runs after the subcommand, but before Main terminates 45 // with either success or the error from the subcommand. 46 PreExit = func() {} 47 // ExitWithFailure determines whether the command exits 48 // with a non-zero exit status. 49 ExitWithFailure bool 50 // CheckCwd checks the current working directory, and possibly 51 // changes it, or aborts the run if needed. 52 CheckCwd = func() {} 53 ) 54 55 var ErrUsage = UsageError("invalid command") 56 57 type UsageError string 58 59 func (ue UsageError) Error() string { 60 return "Usage error: " + string(ue) 61 } 62 63 var ( 64 // mode name to actual subcommand mapping 65 modeCommand = make(map[string]CommandRunner) 66 modeFlags = make(map[string]*flag.FlagSet) 67 wantHelp = make(map[string]*bool) 68 69 // Indirections for replacement by tests 70 Stderr io.Writer = os.Stderr 71 Stdout io.Writer = os.Stdout 72 Stdin io.Reader = os.Stdin 73 74 Exit = realExit 75 // TODO: abstract out vfs operation. should never call os.Stat, os.Open, os.Create, etc. 76 // Only use fs.Stat, fs.Open, where vs is an interface type. 77 // TODO: switch from using the global flag FlagSet and use our own. right now 78 // running "go test -v" dumps the flag usage data to the global stderr. 79 ) 80 81 func realExit(code int) { 82 os.Exit(code) 83 } 84 85 // CommandRunner is the type that a command mode should implement. 86 type CommandRunner interface { 87 Usage() 88 RunCommand(args []string) error 89 } 90 91 type exampler interface { 92 Examples() []string 93 } 94 95 type describer interface { 96 Describe() string 97 } 98 99 // RegisterCommand adds a mode to the list of modes for the main command. 100 // It is meant to be called in init() for each subcommand. 101 func RegisterCommand(mode string, makeCmd func(Flags *flag.FlagSet) CommandRunner) { 102 if _, dup := modeCommand[mode]; dup { 103 log.Fatalf("duplicate command %q registered", mode) 104 } 105 flags := flag.NewFlagSet(mode+" options", flag.ContinueOnError) 106 flags.Usage = func() {} 107 108 var cmdHelp bool 109 flags.BoolVar(&cmdHelp, "help", false, "Help for this mode.") 110 wantHelp[mode] = &cmdHelp 111 modeFlags[mode] = flags 112 modeCommand[mode] = makeCmd(flags) 113 } 114 115 type namedMode struct { 116 Name string 117 Command CommandRunner 118 } 119 120 // TODO(mpl): do we actually need this? I changed usage 121 // to simply iterate over all of modeCommand and it seems 122 // fine. 123 func allModes(startModes []string) <-chan namedMode { 124 ch := make(chan namedMode) 125 go func() { 126 defer close(ch) 127 done := map[string]bool{} 128 for _, name := range startModes { 129 done[name] = true 130 cmd := modeCommand[name] 131 if cmd == nil { 132 panic("bogus mode: " + name) 133 } 134 ch <- namedMode{name, cmd} 135 } 136 var rest []string 137 for name := range modeCommand { 138 if !done[name] { 139 rest = append(rest, name) 140 } 141 } 142 sort.Strings(rest) 143 for _, name := range rest { 144 ch <- namedMode{name, modeCommand[name]} 145 } 146 }() 147 return ch 148 } 149 150 func hasFlags(flags *flag.FlagSet) bool { 151 any := false 152 flags.VisitAll(func(*flag.Flag) { 153 any = true 154 }) 155 return any 156 } 157 158 // Errorf prints to Stderr 159 func Errorf(format string, args ...interface{}) { 160 fmt.Fprintf(Stderr, format, args...) 161 } 162 163 func usage(msg string) { 164 cmdName := filepath.Base(os.Args[0]) 165 if msg != "" { 166 Errorf("Error: %v\n", msg) 167 } 168 Errorf(` 169 Usage: ` + cmdName + ` [globalopts] <mode> [commandopts] [commandargs] 170 171 Modes: 172 173 `) 174 for mode, cmd := range modeCommand { 175 if des, ok := cmd.(describer); ok { 176 Errorf(" %s: %s\n", mode, des.Describe()) 177 } 178 } 179 Errorf("\nExamples:\n") 180 for mode, cmd := range modeCommand { 181 if ex, ok := cmd.(exampler); ok { 182 exs := ex.Examples() 183 if len(exs) > 0 { 184 Errorf("\n") 185 } 186 for _, example := range exs { 187 Errorf(" %s %s %s\n", cmdName, mode, example) 188 } 189 } 190 } 191 192 Errorf(` 193 For mode-specific help: 194 195 ` + cmdName + ` <mode> -help 196 197 Global options: 198 `) 199 flag.PrintDefaults() 200 Exit(1) 201 } 202 203 func help(mode string) { 204 cmdName := os.Args[0] 205 // We can skip all the checks as they're done in Main 206 cmd := modeCommand[mode] 207 cmdFlags := modeFlags[mode] 208 cmdFlags.SetOutput(Stderr) 209 if des, ok := cmd.(describer); ok { 210 Errorf("%s\n", des.Describe()) 211 } 212 Errorf("\n") 213 cmd.Usage() 214 if hasFlags(cmdFlags) { 215 cmdFlags.PrintDefaults() 216 } 217 if ex, ok := cmd.(exampler); ok { 218 Errorf("\nExamples:\n") 219 for _, example := range ex.Examples() { 220 Errorf(" %s %s %s\n", cmdName, mode, example) 221 } 222 } 223 } 224 225 // registerFlagOnce guards ExtraFlagRegistration. Tests may invoke 226 // Main multiple times, but duplicate flag registration is fatal. 227 var registerFlagOnce sync.Once 228 229 var setCommandLineOutput func(io.Writer) // or nil if before Go 1.2 230 231 // Main is meant to be the core of a command that has 232 // subcommands (modes), such as camput or camtool. 233 func Main() { 234 registerFlagOnce.Do(ExtraFlagRegistration) 235 if setCommandLineOutput != nil { 236 setCommandLineOutput(Stderr) 237 } 238 flag.Parse() 239 CheckCwd() 240 241 args := flag.Args() 242 if *FlagVersion { 243 fmt.Fprintf(Stderr, "%s version: %s\n", os.Args[0], buildinfo.Version()) 244 return 245 } 246 if *FlagHelp { 247 usage("") 248 } 249 if len(args) == 0 { 250 usage("No mode given.") 251 } 252 253 mode := args[0] 254 cmd, ok := modeCommand[mode] 255 if !ok { 256 usage(fmt.Sprintf("Unknown mode %q", mode)) 257 } 258 259 cmdFlags := modeFlags[mode] 260 cmdFlags.SetOutput(Stderr) 261 err := cmdFlags.Parse(args[1:]) 262 if err != nil { 263 err = ErrUsage 264 } else { 265 if *wantHelp[mode] { 266 help(mode) 267 return 268 } 269 err = cmd.RunCommand(cmdFlags.Args()) 270 } 271 if ue, isUsage := err.(UsageError); isUsage { 272 if isUsage { 273 Errorf("%s\n", ue) 274 } 275 cmd.Usage() 276 Errorf("\nGlobal options:\n") 277 flag.PrintDefaults() 278 279 if hasFlags(cmdFlags) { 280 Errorf("\nMode-specific options for mode %q:\n", mode) 281 cmdFlags.PrintDefaults() 282 } 283 Exit(1) 284 } 285 PreExit() 286 if err != nil { 287 if !ExitWithFailure { 288 // because it was already logged if ExitWithFailure 289 log.Printf("Error: %v", err) 290 } 291 Exit(2) 292 } 293 }