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  }