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 "" }