github.com/markusbkk/elvish@v0.0.0-20231204143114-91dc52438621/pkg/prog/prog.go (about)

     1  // Package prog provides the entry point to Elvish. Its subpackages correspond
     2  // to subprograms of Elvish.
     3  package prog
     4  
     5  // This package sets up the basic environment and calls the appropriate
     6  // "subprogram", one of the daemon, the terminal interface, or the web
     7  // interface.
     8  
     9  import (
    10  	"errors"
    11  	"flag"
    12  	"fmt"
    13  	"io"
    14  	"os"
    15  
    16  	"github.com/markusbkk/elvish/pkg/logutil"
    17  )
    18  
    19  // DeprecationLevel is a global flag that controls which deprecations to show.
    20  // If its value is X, Elvish shows deprecations that should be shown for version
    21  // 0.X.
    22  var DeprecationLevel = 17
    23  
    24  // Program represents a subprogram.
    25  type Program interface {
    26  	RegisterFlags(fs *FlagSet)
    27  	// Run runs the subprogram.
    28  	Run(fds [3]*os.File, args []string) error
    29  }
    30  
    31  func usage(out io.Writer, fs *flag.FlagSet) {
    32  	fmt.Fprintln(out, "Usage: elvish [flags] [script]")
    33  	fmt.Fprintln(out, "Supported flags:")
    34  	fs.SetOutput(out)
    35  	fs.PrintDefaults()
    36  }
    37  
    38  // Run parses command-line flags and runs the first applicable subprogram. It
    39  // returns the exit status of the program.
    40  func Run(fds [3]*os.File, args []string, p Program) int {
    41  	fs := flag.NewFlagSet("elvish", flag.ContinueOnError)
    42  	// Error and usage will be printed explicitly.
    43  	fs.SetOutput(io.Discard)
    44  
    45  	var log string
    46  	var help bool
    47  	fs.StringVar(&log, "log", "", "a file to write debug log to except for the daemon")
    48  	fs.BoolVar(&help, "help", false, "show usage help and quit")
    49  	fs.IntVar(&DeprecationLevel, "deprecation-level", DeprecationLevel, "show warnings for all features deprecated as of version 0.X")
    50  
    51  	p.RegisterFlags(&FlagSet{FlagSet: fs})
    52  
    53  	err := fs.Parse(args[1:])
    54  	if err != nil {
    55  		if err == flag.ErrHelp {
    56  			// (*flag.FlagSet).Parse returns ErrHelp when -h or -help was
    57  			// requested but *not* defined. Elvish defines -help, but not -h; so
    58  			// this means that -h has been requested. Handle this by printing
    59  			// the same message as an undefined flag.
    60  			fmt.Fprintln(fds[2], "flag provided but not defined: -h")
    61  		} else {
    62  			fmt.Fprintln(fds[2], err)
    63  		}
    64  		usage(fds[2], fs)
    65  		return 2
    66  	}
    67  
    68  	if log != "" {
    69  		err = logutil.SetOutputFile(log)
    70  		if err != nil {
    71  			fmt.Fprintln(fds[2], err)
    72  		}
    73  	}
    74  
    75  	if help {
    76  		usage(fds[1], fs)
    77  		return 0
    78  	}
    79  
    80  	err = p.Run(fds, fs.Args())
    81  	if err == nil {
    82  		return 0
    83  	}
    84  	if err == ErrNextProgram {
    85  		err = errNoSuitableSubprogram
    86  	}
    87  	if msg := err.Error(); msg != "" {
    88  		fmt.Fprintln(fds[2], msg)
    89  	}
    90  	switch err := err.(type) {
    91  	case badUsageError:
    92  		usage(fds[2], fs)
    93  	case exitError:
    94  		return err.exit
    95  	}
    96  	return 2
    97  }
    98  
    99  // Composite returns a Program that tries each of the given programs,
   100  // terminating at the first one that doesn't return NotSuitable().
   101  func Composite(programs ...Program) Program {
   102  	return composite(programs)
   103  }
   104  
   105  type composite []Program
   106  
   107  func (cp composite) RegisterFlags(f *FlagSet) {
   108  	for _, p := range cp {
   109  		p.RegisterFlags(f)
   110  	}
   111  }
   112  
   113  func (cp composite) Run(fds [3]*os.File, args []string) error {
   114  	for _, p := range cp {
   115  		err := p.Run(fds, args)
   116  		if err != ErrNextProgram {
   117  			return err
   118  		}
   119  	}
   120  	// If we have reached here, all subprograms have returned ErrNextProgram
   121  	return ErrNextProgram
   122  }
   123  
   124  var errNoSuitableSubprogram = errors.New("internal error: no suitable subprogram")
   125  
   126  // ErrNextProgram is a special error that may be returned by Program.Run that
   127  // is part of a Composite program, indicating that the next program should be
   128  // tried.
   129  var ErrNextProgram = errors.New("next program")
   130  
   131  // BadUsage returns a special error that may be returned by Program.Run. It
   132  // causes the main function to print out a message, the usage information and
   133  // exit with 2.
   134  func BadUsage(msg string) error { return badUsageError{msg} }
   135  
   136  type badUsageError struct{ msg string }
   137  
   138  func (e badUsageError) Error() string { return e.msg }
   139  
   140  // Exit returns a special error that may be returned by Program.Run. It causes
   141  // the main function to exit with the given code without printing any error
   142  // messages. Exit(0) returns nil.
   143  func Exit(exit int) error {
   144  	if exit == 0 {
   145  		return nil
   146  	}
   147  	return exitError{exit}
   148  }
   149  
   150  type exitError struct{ exit int }
   151  
   152  func (e exitError) Error() string { return "" }