github.com/kolbycrouch/elvish@v0.14.1-0.20210614162631-215b9ac1c423/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 "runtime/pprof" 15 16 "src.elv.sh/pkg/logutil" 17 ) 18 19 // Default port on which the web interface runs. The number is chosen because it 20 // resembles "elvi". 21 const defaultWebPort = 3171 22 23 // DeprecationLevel is a global flag that controls which deprecations to show. 24 // If its value is X, Elvish shows deprecations that should be shown for version 25 // 0.X. 26 var DeprecationLevel = 15 27 28 // SetDeprecationLevel sets ShowDeprecations to the given value, and returns a 29 // function to restore the old value. 30 func SetDeprecationLevel(level int) func() { 31 save := DeprecationLevel 32 DeprecationLevel = level 33 return func() { DeprecationLevel = save } 34 } 35 36 // Flags keeps command-line flags. 37 type Flags struct { 38 Log, CPUProfile string 39 40 Help, Version, BuildInfo, JSON bool 41 42 CodeInArg, CompileOnly, NoRc bool 43 44 Web bool 45 Port int 46 47 Daemon bool 48 Forked int 49 50 Bin, DB, Sock string 51 } 52 53 func newFlagSet(stderr io.Writer, f *Flags) *flag.FlagSet { 54 fs := flag.NewFlagSet("elvish", flag.ContinueOnError) 55 fs.SetOutput(stderr) 56 fs.Usage = func() { usage(stderr, fs) } 57 58 fs.StringVar(&f.Log, "log", "", "a file to write debug log to except for the daemon") 59 fs.StringVar(&f.CPUProfile, "cpuprofile", "", "write cpu profile to file") 60 61 fs.BoolVar(&f.Help, "help", false, "show usage help and quit") 62 fs.BoolVar(&f.Version, "version", false, "show version and quit") 63 fs.BoolVar(&f.BuildInfo, "buildinfo", false, "show build info and quit") 64 fs.BoolVar(&f.JSON, "json", false, "show output in JSON. Useful with -buildinfo.") 65 66 // The `-i` option is for compatibility with POSIX shells so that programs, such as the `script` 67 // command, will work when asked to launch an interactive Elvish shell. 68 fs.Bool("i", false, "force interactive mode; currently ignored") 69 fs.BoolVar(&f.CodeInArg, "c", false, "take first argument as code to execute") 70 fs.BoolVar(&f.CompileOnly, "compileonly", false, "Parse/Compile but do not execute") 71 fs.BoolVar(&f.NoRc, "norc", false, "run elvish without invoking rc.elv") 72 73 fs.BoolVar(&f.Web, "web", false, "run backend of web interface") 74 fs.IntVar(&f.Port, "port", defaultWebPort, "the port of the web backend") 75 76 fs.BoolVar(&f.Daemon, "daemon", false, "run daemon instead of shell") 77 78 fs.StringVar(&f.Bin, "bin", "", "path to the elvish binary") 79 fs.StringVar(&f.DB, "db", "", "path to the database") 80 fs.StringVar(&f.Sock, "sock", "", "path to the daemon socket") 81 82 fs.IntVar(&DeprecationLevel, "deprecation-level", DeprecationLevel, "show warnings for all features deprecated as of version 0.X") 83 84 return fs 85 } 86 87 func usage(out io.Writer, f *flag.FlagSet) { 88 fmt.Fprintln(out, "Usage: elvish [flags] [script]") 89 fmt.Fprintln(out, "Supported flags:") 90 f.PrintDefaults() 91 } 92 93 // Run parses command-line flags and runs the first applicable subprogram. It 94 // returns the exit status of the program. 95 func Run(fds [3]*os.File, args []string, programs ...Program) int { 96 f := &Flags{} 97 fs := newFlagSet(fds[2], f) 98 err := fs.Parse(args[1:]) 99 if err != nil { 100 // Error and usage messages are already shown. 101 return 2 102 } 103 104 // Handle flags common to all subprograms. 105 if f.CPUProfile != "" { 106 f, err := os.Create(f.CPUProfile) 107 if err != nil { 108 fmt.Fprintln(fds[2], "Warning: cannot create CPU profile:", err) 109 fmt.Fprintln(fds[2], "Continuing without CPU profiling.") 110 } else { 111 pprof.StartCPUProfile(f) 112 defer pprof.StopCPUProfile() 113 } 114 } 115 116 if f.Daemon { 117 // We expect our stdout file handle is open on a unique log file for the daemon to write its 118 // log messages. See daemon.Spawn() in pkg/daemon. 119 logutil.SetOutput(fds[1]) 120 } else if f.Log != "" { 121 err = logutil.SetOutputFile(f.Log) 122 if err != nil { 123 fmt.Fprintln(fds[2], err) 124 } 125 } 126 127 if f.Help { 128 fs.SetOutput(fds[1]) 129 usage(fds[1], fs) 130 return 0 131 } 132 133 p := findProgram(f, programs) 134 if p == nil { 135 fmt.Fprintln(fds[2], "program bug: no suitable subprogram") 136 return 2 137 } 138 139 err = p.Run(fds, f, fs.Args()) 140 if err == nil { 141 return 0 142 } 143 if msg := err.Error(); msg != "" { 144 fmt.Fprintln(fds[2], msg) 145 } 146 switch err := err.(type) { 147 case badUsageError: 148 usage(fds[2], fs) 149 case exitError: 150 return err.exit 151 } 152 return 2 153 } 154 155 func findProgram(f *Flags, programs []Program) Program { 156 for _, program := range programs { 157 if program.ShouldRun(f) { 158 return program 159 } 160 } 161 return nil 162 } 163 164 // BadUsage returns an error that may be returned by Program.Main, which 165 // requests the main program to print out a message, the usage information and 166 // exit with 2. 167 func BadUsage(msg string) error { return badUsageError{msg} } 168 169 type badUsageError struct{ msg string } 170 171 func (e badUsageError) Error() string { return e.msg } 172 173 // Exit returns an error that may be returned by Program.Main, which requests the 174 // main program to exit with the given code. If the exit code is 0, it returns nil. 175 func Exit(exit int) error { 176 if exit == 0 { 177 return nil 178 } 179 return exitError{exit} 180 } 181 182 type exitError struct{ exit int } 183 184 func (e exitError) Error() string { return "" } 185 186 // Program represents a subprogram. 187 type Program interface { 188 // ShouldRun returns whether the subprogram should run. 189 ShouldRun(f *Flags) bool 190 // Run runs the subprogram. 191 Run(fds [3]*os.File, f *Flags, args []string) error 192 }