src.elv.sh@v0.21.0-dev.0.20240515223629-06979efb9a2a/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 "flag" 11 "fmt" 12 "io" 13 "os" 14 15 "src.elv.sh/pkg/logutil" 16 ) 17 18 // DeprecationLevel is a global flag that controls which deprecations to show. 19 // If its value is X, Elvish shows deprecations that should be shown for version 20 // 0.X. 21 var DeprecationLevel = 20 22 23 // Program represents a subprogram. 24 type Program interface { 25 RegisterFlags(fs *FlagSet) 26 // Run runs the subprogram. 27 Run(fds [3]*os.File, args []string) error 28 } 29 30 func usage(out io.Writer, fs *flag.FlagSet) { 31 fmt.Fprintln(out, "Usage: elvish [flags] [script] [args]") 32 fmt.Fprintln(out, "Supported flags:") 33 fs.SetOutput(out) 34 fs.PrintDefaults() 35 } 36 37 // Run parses command-line flags and runs the first applicable subprogram. It 38 // returns the exit status of the program. 39 func Run(fds [3]*os.File, args []string, p Program) int { 40 fs := flag.NewFlagSet("elvish", flag.ContinueOnError) 41 // Error and usage will be printed explicitly. 42 fs.SetOutput(io.Discard) 43 44 var log string 45 var help bool 46 fs.StringVar(&log, "log", "", 47 "Path to a file to write debug logs") 48 fs.BoolVar(&help, "help", false, 49 "Show usage help and quit") 50 fs.IntVar(&DeprecationLevel, "deprecation-level", DeprecationLevel, 51 "Show warnings for all features deprecated as of version 0.X") 52 53 p.RegisterFlags(&FlagSet{FlagSet: fs}) 54 55 err := fs.Parse(args[1:]) 56 if err != nil { 57 if err == flag.ErrHelp { 58 // (*flag.FlagSet).Parse returns ErrHelp when -h or -help was 59 // requested but *not* defined. Elvish defines -help, but not -h; so 60 // this means that -h has been requested. Handle this by printing 61 // the same message as an undefined flag. 62 fmt.Fprintln(fds[2], "flag provided but not defined: -h") 63 } else { 64 fmt.Fprintln(fds[2], err) 65 } 66 usage(fds[2], fs) 67 return 2 68 } 69 70 if log != "" { 71 err = logutil.SetOutputFile(log) 72 if err == nil { 73 defer logutil.SetOutput(io.Discard) 74 } else { 75 fmt.Fprintln(fds[2], err) 76 } 77 } 78 79 if help { 80 usage(fds[1], fs) 81 return 0 82 } 83 84 err = p.Run(fds, fs.Args()) 85 if err == nil { 86 return 0 87 } 88 if msg := err.Error(); msg != "" { 89 fmt.Fprintln(fds[2], msg) 90 } 91 switch err := err.(type) { 92 case badUsageError: 93 usage(fds[2], fs) 94 case exitError: 95 return err.exit 96 } 97 return 2 98 } 99 100 // Composite returns a Program that tries each of the given programs, 101 // terminating at the first one that doesn't return NotSuitable(). 102 func Composite(programs ...Program) Program { 103 return composite(programs) 104 } 105 106 type composite []Program 107 108 func (cp composite) RegisterFlags(f *FlagSet) { 109 for _, p := range cp { 110 p.RegisterFlags(f) 111 } 112 } 113 114 func (cp composite) Run(fds [3]*os.File, args []string) error { 115 var cleanups []func([3]*os.File) 116 for _, p := range cp { 117 err := p.Run(fds, args) 118 if np, ok := err.(nextProgramError); ok { 119 cleanups = append(cleanups, np.cleanups...) 120 } else { 121 for i := len(cleanups) - 1; i >= 0; i-- { 122 cleanups[i](fds) 123 } 124 return err 125 } 126 } 127 // If we have reached here, all subprograms have returned ErrNextProgram 128 return NextProgram(cleanups...) 129 } 130 131 // NextProgram returns a special error that may be returned by [Program.Run] 132 // that is part of a [Composite] program, indicating that the next program 133 // should be tried. It can carry a list of cleanup functions that should be run 134 // in reverse order before the [Composite] program finishes. 135 func NextProgram(cleanups ...func([3]*os.File)) error { return nextProgramError{cleanups} } 136 137 type nextProgramError struct{ cleanups []func([3]*os.File) } 138 139 // If this error ever gets printed, it has been bubbled to [Run] when all 140 // programs have returned this error type. 141 func (e nextProgramError) Error() string { 142 return "internal error: no suitable subprogram" 143 } 144 145 // BadUsage returns a special error that may be returned by Program.Run. It 146 // causes the main function to print out a message, the usage information and 147 // exit with 2. 148 func BadUsage(msg string) error { return badUsageError{msg} } 149 150 type badUsageError struct{ msg string } 151 152 func (e badUsageError) Error() string { return e.msg } 153 154 // Exit returns a special error that may be returned by Program.Run. It causes 155 // the main function to exit with the given code without printing any error 156 // messages. Exit(0) returns nil. 157 func Exit(exit int) error { 158 if exit == 0 { 159 return nil 160 } 161 return exitError{exit} 162 } 163 164 type exitError struct{ exit int } 165 166 func (e exitError) Error() string { return "" }