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  }