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