github.com/btwiuse/jiri@v0.0.0-20191125065820-53353bcfef54/cmdline/env.go (about)

     1  // Copyright 2015 The Vanadium Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cmdline
     6  
     7  import (
     8  	"fmt"
     9  	"io"
    10  	"os"
    11  	"strconv"
    12  
    13  	"github.com/btwiuse/jiri/envvar"
    14  	"github.com/btwiuse/jiri/lookpath"
    15  	"github.com/btwiuse/jiri/textutil"
    16  	"github.com/btwiuse/jiri/timing"
    17  )
    18  
    19  // EnvFromOS returns a new environment based on the operating system.
    20  func EnvFromOS() *Env {
    21  	return &Env{
    22  		Stdin:  os.Stdin,
    23  		Stdout: os.Stdout,
    24  		Stderr: os.Stderr,
    25  		Vars:   envvar.SliceToMap(os.Environ()),
    26  		Timer:  timing.NewTimer("root"),
    27  	}
    28  }
    29  
    30  // Env represents the environment for command parsing and running.  Typically
    31  // EnvFromOS is used to produce a default environment.  The environment may be
    32  // explicitly set for finer control; e.g. in tests.
    33  type Env struct {
    34  	Stdin  io.Reader
    35  	Stdout io.Writer
    36  	Stderr io.Writer
    37  	Vars   map[string]string // Environment variables
    38  	Timer  *timing.Timer
    39  
    40  	// Usage is a function that prints usage information to w.  Typically set by
    41  	// calls to Main or Parse to print usage of the leaf command.
    42  	Usage func(env *Env, w io.Writer)
    43  
    44  	// Comamnd name
    45  	CommandName string
    46  	// command flags
    47  	CommandFlags map[string]string
    48  }
    49  
    50  func (e *Env) clone() *Env {
    51  	return &Env{
    52  		Stdin:  e.Stdin,
    53  		Stdout: e.Stdout,
    54  		Stderr: e.Stderr,
    55  		Vars:   envvar.CopyMap(e.Vars),
    56  		Usage:  e.Usage,
    57  		Timer:  e.Timer, // use the same timer for all operations
    58  	}
    59  }
    60  
    61  // UsageErrorf prints the error message represented by the printf-style format
    62  // and args, followed by the output of the Usage function.  Returns ErrUsage to
    63  // make it easy to use from within the Runner.Run function.
    64  func (e *Env) UsageErrorf(format string, args ...interface{}) error {
    65  	return usageErrorf(e, e.Usage, format, args...)
    66  }
    67  
    68  // TimerPush calls e.Timer.Push(name), only if the Timer is non-nil.
    69  func (e *Env) TimerPush(name string) {
    70  	if e.Timer != nil {
    71  		e.Timer.Push(name)
    72  	}
    73  }
    74  
    75  // TimerPop calls e.Timer.Pop(), only if the Timer is non-nil.
    76  func (e *Env) TimerPop() {
    77  	if e.Timer != nil {
    78  		e.Timer.Pop()
    79  	}
    80  }
    81  
    82  // LookPath returns the absolute path of the executable with the given name,
    83  // based on the directories in PATH.  Calls lookpath.Look.
    84  func (e *Env) LookPath(name string) (string, error) {
    85  	e.TimerPush("lookpath " + name)
    86  	defer e.TimerPop()
    87  	return lookpath.Look(e.Vars, name)
    88  }
    89  
    90  // LookPathPrefix returns the absolute paths of all executables with the given
    91  // name prefix, based on the directories in PATH.  Calls lookpath.LookPrefix.
    92  func (e *Env) LookPathPrefix(prefix string, names map[string]bool) ([]string, error) {
    93  	e.TimerPush("lookpathprefix " + prefix)
    94  	defer e.TimerPop()
    95  	return lookpath.LookPrefix(e.Vars, prefix, names)
    96  }
    97  
    98  func usageErrorf(env *Env, usage func(*Env, io.Writer), format string, args ...interface{}) error {
    99  	fmt.Fprint(env.Stderr, "ERROR: ")
   100  	fmt.Fprintf(env.Stderr, format, args...)
   101  	fmt.Fprint(env.Stderr, "\n\n")
   102  	if usage != nil {
   103  		usage(env, env.Stderr)
   104  	} else {
   105  		fmt.Fprint(env.Stderr, "usage error\n")
   106  	}
   107  	return ErrUsage
   108  }
   109  
   110  // defaultWidth is a reasonable default for the output width in runes.
   111  const defaultWidth = 80
   112  
   113  func (e *Env) width() int {
   114  	if width, err := strconv.Atoi(e.Vars["CMDLINE_WIDTH"]); err == nil && width != 0 {
   115  		return width
   116  	}
   117  	if _, width, err := textutil.TerminalSize(); err == nil && width != 0 {
   118  		return width
   119  	}
   120  	return defaultWidth
   121  }
   122  
   123  func (e *Env) style() style {
   124  	style := styleCompact
   125  	style.Set(e.Vars["CMDLINE_STYLE"])
   126  	return style
   127  }
   128  
   129  func (e *Env) prefix() string {
   130  	return e.Vars["CMDLINE_PREFIX"]
   131  }
   132  
   133  func (e *Env) firstCall() bool {
   134  	return e.Vars["CMDLINE_FIRST_CALL"] == ""
   135  }
   136  
   137  // style describes the formatting style for usage descriptions.
   138  type style int
   139  
   140  const (
   141  	styleCompact   style = iota // Default style, good for compact cmdline output.
   142  	styleFull                   // Similar to compact but shows all global flags.
   143  	styleGoDoc                  // Good for godoc processing.
   144  	styleShortOnly              // Only output short description.
   145  )
   146  
   147  func (s *style) String() string {
   148  	switch *s {
   149  	case styleCompact:
   150  		return "compact"
   151  	case styleFull:
   152  		return "full"
   153  	case styleGoDoc:
   154  		return "godoc"
   155  	case styleShortOnly:
   156  		return "shortonly"
   157  	default:
   158  		panic(fmt.Errorf("unhandled style %d", *s))
   159  	}
   160  }
   161  
   162  // Set implements the flag.Value interface method.
   163  func (s *style) Set(value string) error {
   164  	switch value {
   165  	case "compact":
   166  		*s = styleCompact
   167  	case "full":
   168  		*s = styleFull
   169  	case "godoc":
   170  		*s = styleGoDoc
   171  	case "shortonly":
   172  		*s = styleShortOnly
   173  	default:
   174  		return fmt.Errorf("unknown style %q", value)
   175  	}
   176  	return nil
   177  }